<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 3.3 Linux 編程基礎 ### 3.3.1 你必須掌握的技能 根據筆者的經驗,要想在 Linux 上能夠”無障礙”的用 Qt 編程,掌握必要的 Linux 技 能是必需的。以下是筆者列出的一些技能,供參考: + 了解各個發行版的特點,能夠根據需求,挑選和安裝適合自己的發行版; + 掌握常見的軟件包管理工具的使用,包括 GUI 工具和編譯命令,能夠熟練安裝軟件包 + 熟悉 Linux 文件系統結構,能夠熟練使用文件系統操作命令,配置文件的權限 + 了解 X Window 系統的原理和組成結構,必要時可以自行配置 + 能夠熟練使用常見的集成式桌面環境,如 KDE 和 GNOME + 熟悉 Linux 系統的帳號管理,能夠熟練管理用戶與組的帳號,并使用常用命令 + 能夠熟練配置網絡連接,包括局域網和 Internet + 熟悉 Shell,了解 Shell 的種類,熟悉與 Shell 有關的配置文件,并能夠根據需要修改 + 熟悉 Linux 文件壓縮的方法,掌握文件壓縮的常見命令 + 能夠熟練使用 man 命令執行在線查詢和獲取幫助,能夠熟練使用查找命令 + 能夠熟練掌握各個發行版上基礎編程環境的配置,主要包括 GCC、GDB 等 + 能夠熟練使用一種代碼編輯器,如 vi、emacs 等,推薦 vi + 了解 Linux 系統下打包軟件的常見方法 在下面的各節中,筆者將結合編程實際和本書內容的需要,有選擇的向大家介紹這些技能,更為詳盡的說明,請參考相關的 Linux 書籍。 ### 3.3.2 文件系統管理 文件系統操作系統實際用來保存和管理各種文件的方法。由于每種操作系統支持的文件系統數量和類型都不同,因此在了解系統運行前,必須對文件系統的結構有所了解。尤其在 Linux 系統中,任何軟硬件都被視為文件,所以這一部分的內容就尤為重要。 1\.Linux 文件系統架構 操作系統中的文件系統(File System)可說是最基本的架構,因為幾乎所有與用戶、 應用程序或安全性模型相互通信的方法,都與文件保存的類型息息相關。 (1) 文件類型 簡單來說,文件系統可以分為兩種類型。 + 共享與非共享文件:共享文件是指允許其他主機訪問的文件,而非共享文件則只供 本機使用 + 可變與固定文件:可變文件是指不需要通過系統管理員修改,即可自動更改內容的 文件,例如數據庫文件;而固定文件則是指內容不會自動更改的文件,例如一般的文件或二 進制文件。 Linux 文件系統采用層次式的樹狀目錄結構,此結構的最上層是根目錄 “/”,然后在 根目錄下再建立其他的目錄。雖然目錄的名稱可以自定義,但是某些特殊的目錄名稱包含重 要的功能,所以不可隨意將它們改名,否則會造成系統錯誤。 因為 Linux 允許不同的廠商及個人修改其操作系統,所以常會造成目錄名稱不統一的 情況,有鑒于此,目前有一套規范文件目錄的命名及存放標準,它被稱為 Filesystem Hierarchy Standard(FHS),這也是大多數 Linux 發行版遵循的標準,如果需要詳細的說 明,請參考以下網站說明:http://proton.pathname.com/fhs/。 (2) Linux 默認目錄 在安裝 Linux 時,系統會建立一些默認的目錄, 并且每個目錄都有其特殊功能,如圖 3-5 所示。 ![](https://box.kancloud.cn/2016-01-22_56a1a144520dc.png) 圖 3-5 Unix/Linux 文件系統結構圖 以下是這些目錄的說明: + /:Linux 文件系統的最頂層根目錄 + /bin:Binary 的 縮寫,存放用戶的可執行程序,例如 ls、cp 和 mv 等,也包含其 他的 Shell,例如 Bash 和 csh + /boot:操作系統啟動時需要的文件,例如 vmlinuz 和 initrd.imp。這些文件如果 損壞,常會導致系統無法正常啟動,因此最好不要任意改動 + /dev:接口設備文件目錄,例如 hda 表示第一塊 IDE 硬盤 + /etc:有關系統設置與管理的文件,例如 password。目前保存在 etc/目錄中的文 件都不是二進制文件,之前在此目錄中的二進制文件都已移到 /sbin 或/bin 目錄 + etc/X11:X Window 系統的設置目錄 + /home:一般用戶的主目錄或 FTP 站臺目錄 + /lib:僅包含執行/bin 和/sbin 目錄中的二進制文件時所需的共享類庫( shared library) + /mnt:各項設備的文件掛載(Mount)點,例如光盤的默認掛載點是/mnt/cdrom, 而/mnt/floppy 是軟驅的默認掛載點 + /opt:這個目錄通常提供一個空間,供較大型且固定的應用程序軟件包保存文件使 用,這樣可以避免將文件分散到整個文件系統 + /proc:目前系統內核與程序執行的信息,它和用ps命令看到的內容相同 + /root:root 管理員主目錄,其他用戶的主目錄都位于 /home 目錄中 + /sbin:是 System Binary 的縮寫,此目錄存放的是系統啟動時需要執行的程序, 比如 swapon + /tmp:Tempoary 的縮寫,它是用來存放臨時文件的目錄 + /usr:存放用戶使用的系統命令,以及應用程序等信息 + /usr/bin:存放用戶可執行程序,例如 finger 和 mdir 等 + /usr/include:保存供 C 語言加載的頭文件 + /usr/include/X11:保存供 X Window 程序加載的頭文件 + /usr/lib:用戶類庫 + /usr/lib/X11:X11 的類庫 + /usr/local:提供自行安裝的應用程序位置 + /usr/sbin:存放非經常性使用的程序,例如 showmount + /usr/src:保存程序的原始文件 + /usr/X11R6/bin:存放 X Window 系統的執行程序 + /var:Variable 的縮寫,具有可變性質的相關程序目錄,例如 log、spool 和 named 等 (3) Linux 文件名稱 Linux 中的文件名稱最長可允許 256 個字符,而這些字符可用 A~Z、0~9、_、-等字符 來命名。 與其他操作系統相比,比如 DOS 和 Windows,Linux 最大的不同是,它并沒有擴展名的 概念,也就是說文件的名稱和該文件的類型并沒有直接的關聯,例如 sample.txt 可能是執 行文件,sample.exe 也有可能是文本文件,甚至可以不用擴展名。 另一個特性是文件名稱區分大小寫(Case Sensitive),這也是習慣 DOS 和 Windows 平臺用戶最難適應的一點。但所有的 Unix(包括 Linux)都遵循這條原則,例如 sample.txt、Sample.txt、SAMPLE.txt、sampLE.txt 在 Linux 上代表不同的文件,但在 DOS 和 Windows 平臺上卻是指同一個文件。 (4) Ext3 文件系統 Linux 可說是一種兼容性很高的操作系統。它可以支持的文件系統有很多,例如 msdos、ntfs、vfat、iso9660、minix 等等,也就是說它可以和其他許多不同的文件或操作 系統同時存在于硬盤中,這也是 Linux 足以傲視其他操作系統的地方之一。 與 micorsoft Windows 操作系統不同的是,Linux 并沒有分區的概念,也就是說,它不 會將文件的保存位置指定為磁盤驅動器 C、D、E 等等,而是使用樹狀的 ext3fs(Third Extended File System)作為主要的文件系統(有時也簡稱為 ext3)。 ext3fs 是大多數 Linux 默認的文件系統,它主要是在原有的 ext2fs 系統上增加了日志 功能,并且在數據的有效性、完整性、訪問速度方面有了大幅提高。 (5) Linux 文件路徑 在此我們要介紹 Linux 文件路徑表示法。因為在 Linux 的世界中沒有微軟產品的磁盤 驅動器的概念,也就是說沒有所謂的磁盤驅動器 C、D 等表示法,它是利用目錄與子目錄的 層次式(Hierarchical)概念來表示文件保存位置的。 所以你需要先建立起這個新概念,否則可能會因為先前的概念而產生學習 Linux 的障 礙。一般來說,Linux 的文件路徑分為兩類:絕對路徑和相對路徑。 所謂絕對路徑,就是指以根目錄(/)為起始點來表示的路徑,例如 “/etc/ppp/peers/isdn/avm”就是絕對路徑,也就是說,如果一個路徑的表示法是以根目 錄(/)開頭,那就屬于絕對路徑;如果不是以根目錄開頭,就稱為相對路徑。 相對路徑是指從當前的目錄算起。我們以剛才的絕對路徑為例,如果現在工作的目錄 是/etc/ppp,那么用相對路徑來表示就是“./peers/isdn/avm”(也可以省略為 peers/isdn/avm)。 因為系統會自動在此路徑前加上當前的工作目錄位置,所以適當的使用相對路徑可以 節省輸入時間,并避免錯誤產生。 ### 3.3.3 X Window 系統 許多人在抗拒學習 Linux 時,最大的借口之一就是命令和參數多如牛毛,特別是慣用 微軟 Windows 操作系統的人更難接受。不過目前在 Linux 上已經大幅進步的圖形界面功 能,使得 X Window 系統成為學習 Linux 時的另一種選擇。 1\.X Window 系統基礎 X Window 系統是一種圖形化的操作環境,它可以在 Unix 和 Linux 操作系統上提供 GUI(Graphical User Interface,圖形用戶界面)操作界面,同時它也有以下幾種不同的名 稱:X、X 視窗、X11、X11R7。 在此特別提醒一點,雖然以上名稱都可用來表示 X Window 系統,但卻不可稱為 “Windows”,因為 Windows 一詞已被 Microsoft 公司注冊,所以切勿使用 Windows,以免 侵犯該公司的注冊商標。 (1) X Window 系統的起源 在 X Window 系統出現前,其實已經有很多公司在發展 Unix 用戶圖形界面,但由于每 家公司開發的規范不一,因此在兼容性方面表現不佳,這種情形直到 X Window 系統推出后 才得以解決。 X Window 系統起源于 1984 年的雅典娜(Project Athena),它是由麻省理工學院(MIT)與 Digital Equipment 公司合作開發的圖形界面系統,因為它以斯坦福大學的 W Window 系統為基礎,所以命名為 X Window 系統(因為字母 X 位于 W 之后)。 為了確保 X Window 系統的持續發展,MIT 于 1988 年成立 X Consortium,它由 X Window 系統的主要設計者 Robert W.Scheifler 負責。之后由于 X Window 系統引起許多公司的興趣,所以新版的 X Window 系統不斷問世。 (2) X Window 系統組成 X Window 系統采用客戶端-服務器架構,其中主要的組件為: X Server 和 X Client。 前者負責驅動顯示卡和各種圖形的顯示,同時也會驅動其他輸入設備,使客戶端可以通過這 些輸入界面與應用程序通信。而后者指實際執行的應用程序,它會向 X Server 提出服務請 求,以得到響應的顯示畫面。 除了 X Server 和 X Client 之外,在 X Window 系統中還包含其它組件,如圖所示,以 下是這些組件的說明: X Protocol 介于 X Server 和 X Client 間用于溝通的通信協議。因為基本的 X Window 系統并不提 供用戶界面,比如按鈕和菜單等組件,所以必須依靠 X Protocol 中的程序來提供這類功 能,否則單純的 X Window 系統無法滿足客戶端的要求。 X Library 最底層的程序界面,它的主要功能是存取 X Protocol 服務,這對于圖形程序編寫非常 重要,常見的 X Library 有:Xlib、Motif、Qt 和 GTK+等。 X Toolkit 包含在 X Library 中的應用程序發展工具,它提供了 X Window 設計時需要的基本函 數,避免了程序開發時必須自行設計所有組件的不便,例如滾動條和功能鈕。這些組件也稱 為 widgets。目前 X Toolkit 的種類很多,較常見的有:Motif Development Toolkit、 OpenLook Toolkit、GTK+、TCL/TK、XForms 和 X Toolkit(Xt)等。 (3) X Window 系統的特點 X Window 系統的特點可以歸納如下: + 圖形化界面 X Window 系統是在 Linux 中唯一的圖形界面系統,但是可以搭配多套窗口管理程序使 用,是比 Windows 產品更具有彈性設計。如果希望修改某些窗口管理程序的內容,可以用 它的源代碼進行修改。 + 支持多種應用程序 目前在 X Window 系統中可使用的應用程序越來越多,文字處理、多媒體、圖形圖像、 游戲軟件、因特網、甚至系統管理工具,都有免費的圖形化工具可供使用。這除了有助于消 除用戶對于文字界面的陌生感,還可以使其可能逐步取代 Windows 而成為個人工作站的選 擇。 + 彈性設計 因為在 X Window 系統的設計中,X Server 只負責基本的顯示及終端的控制,而其余的部分都是由 X Client 處理,所以這種設計不受操作系統的限制,突同的操作系統都可以使用 X Server。特別是在 Unix 的多任務環境中,更能發揮其優異的特性。 + 客戶端-服務器架構 X Window 系統采用客戶端-服務器架構,它將系統顯示功能與應用程序分別用 X Server(X11R7)和 X Client 來執行,這種架構最大的優勢是,執行程序( X Client)和 顯示結果(X Server)的主機可以是不同的兩部計算機。 舉例來說,如果希望在主機上使用 firefox 來瀏覽網頁,但是主機尚未安裝 firefox, 而網絡的某個工作站上裝有 firefox,此時即可連接此工作站并執行 firefox,再將結果返 回本地主機。 在以上的例子中,本地主機擔任 X Server 的角色,它只負責顯示結果,而遠程裝有 firefox 的 Linux 工作站則是 X Client,它負責執行程序,在網絡間負責傳遞兩端信息的 機制則為 X Protocol。 通過 X Protocol 這個媒介,所有的主機都能使用此軟件,而不需要在每臺計算機上安 裝相同的軟件。因此建議將大型且需要高速運算的軟件交由能力較強的工作站來處理,然后 再用網絡顯示執行結果。 2\.X11R7 X11R7 是目前多數主流 Linux 發行版使用的 X Server,它發布于 2005 年 12 月,與之 前的 X11R6.9 相比,采用了模塊化的設計。 (1) X11R7 重要目錄 與 X11R7 有關的軟件,大多放在/usr 及其子目錄中,以下是較為重要的目錄的說明。 + /usr/bin:存放 X Server 和不同的 X Client + /usr/include:開發 X Client 和圖形所需的文件路徑 + /usr/lib:X Server 和 X Client 所需的類庫目錄 + /usr/lib/X11:保存多項資源,例如字體和文件等 + /usr/lib/xorg/moudles:包含驅動程序與多種 X Server 模塊 + /usr/X11/man:保存 X11 程序編寫時的手冊說明 (2) /etc/X11/xorg.conf 文件 在安裝 Linux 時如果沒有設置 X Window 系統,之后必須先設置鼠標、鍵盤、顯示器以 及顯示卡等,才能成功啟用 X Window 系統,而這些設置都記錄在/etc/X11/xorg.conf 文件 中,由此可見這個文件的重要性。 這個文件由數個 Section/EndSection 的區塊組成,每個區塊的格式如下: ``` Section “Section 名稱” 選項名稱 “選項值” 選項名稱 “選項值” 選項名稱 “選項值” ...... EndSection ``` 在/etc/X11/xorg.conf 文件中,有多個區塊是非常重要的,包括 ServerLayout、 Files、Moudle、InputDevice、Monitor、Device、Screen 和 DRI 等。從字名上不難看出它 們的用途,詳細的設置請參閱相應的 Linux 發行版手冊,這里不再贅述。 3\.集成式桌面環境 慣用 Windows 操作系統的用戶可能沒有想過,為什么每個人的右鍵菜單、開始菜單、 所有程序以及其它的系統設置都是如出一轍?能不能隨著用戶的喜好進行修改呢? 的確,Windows 系統使用單一的圖形界面,并且用戶無法改變它。而 Linux 在這方面就 具有較大的彈性,用戶可以依其喜好隨時改變圖形界面,這就是所謂的集成式桌面環境。在 我們使用 Qt 開發的時候,經常用到的兩種桌面環境是 GNOME 和 KDE。 (1) GNOME GNOME 是 GNU Network Object Model Environment 的縮寫,它屬于 GNU(GNU‘s Not Unix)項目的一部分,這個項目始于 1984 年,目的是發展一個完全免費的類 Unix 操作系 統。GNOME 目前是許多 Linux 發行版默認的桌面環境。 除了包含功能強大的組件外,GNOME 也具有靈活的可配置性,所以可以根據個人的喜好 和習慣設置桌面環境。 (2) KDE KDE(K Desktop Environment)是目前 Linux 兩大集成式桌面環境之一,它與 1996 年 10 月由 Lyx 的原創者 Matthias Ettich 發起,從而促成了 KDE 計劃的產生。 與 GNOME 最大的不同是,KDE 原本是使用 QPL(商業版權)發展的,而 GNOME 則是遵循 GNU 協議的,因此在剛開始時,KDE 的推廣遇到了一定的障礙。直到 Qt 決定使用 GPL 協議 后,KDE 的發展又進入了快車道。 Linux 桌面環境的使用相對比較容易,可以參閱系統提供的幫助。 ### 3.3.4 常用命令 下面我們介紹一些常用的命令,它們是經常會被用到的,需要熟練掌握。這里并不展 開對各個命令的詳細說明,使用時請參考相關書籍,也可以使用 man 命令來獲得幫助。 表 3-4 Linux 常用命令說明 | man | 獲得聯機幫助,是類 Unix 用戶的在線幫助手冊 | |:--- |:--- | | cd | 切換當前路徑命令 | | pwd | 顯示當前路徑 | | ls | 顯示目錄下面的文件和子目錄情況 | | chmod | 變更文件和目錄的屬性 | | mkdir | 建立目錄 | | rm | 刪除目錄或文件 | | su | 切換用戶登錄到 shell,常見從一般用戶到 root 用戶或者相反順序 | | exec | 執行程序,并且不返回到當前 shell | | ldd | 查看應用程序使用的動態庫 | | nm | 查看程序或庫的調試信息 | | objdump | 查看程序或庫的信息 | | env | 查看環境變量 | | grep | 從文件中查找字符串 | | find | 查找文件 | | which | 查找命令的可執行文件 | | uname | 查看操作系統版本 | | ps | 查看進程信息 | | top | 查看系統資源信息 | | vmstat | 查看系統虛擬機各資源信息 | | vi/vim | 使用 vi/vim 編輯器 | | make | 處理工程文件,生成可執行文件或庫或其他資源文件 | | gdb/dbx | 調試工具命令 | | strace/ltrace | 跟蹤程序調用的系統函數情況 | | file | 查看文件的格式 | | fuser | 查看進程使用了哪些文件 | ### 3.3.5 Shell 應用 Shell 在操作系統中的作用就如同是翻譯員,它在用戶和操作系統之間傳遞信息。如果 少了它的運行,那么用戶和操作系統之間將被完全阻隔而無法溝通。因此,了解 Shell 是 深入 Linux 核心、學習 Linux 上 Qt 開發的基本功課之一。 1\.Shell 基礎 Shell-如同其名字一樣,它就像是一個殼,而這個殼介于用戶和操作系統( Kernel)中間,負責將用戶的命令解釋為操作系統可以接受的低級語言,同時將操作系統的響應信息 以用戶了解的方式來顯示,這樣可以避免用戶執行不當的命令而對系統產生損害。圖描述的是 Shell 這種角色。 每個用戶在登錄 Linux 后,系統會出現不同的提示符號,如#、$或~等,之后就可以輸 入需要的命令。如果命令正確,系統就可按照命令的要求來執行,直到用戶注銷系統為止。 在登錄到注銷期間,用戶輸入的每個命令都會經過解釋并執行,而這個負責的機制就是 Shell。 (1) 命令的類型 一般用戶的命令可分為兩大類:程序和 Shell 內置命令。如果該命令為程序類型,那 么 Shell 會找出該程序,然后將控制權交給內核,并由內核負責執行該程序;而在內核將 程序執行完畢后,再將控制權交給 Shell。但如果是 Shell 內置命令,則由 Shell 直接響 應,因此速度較快。 要判斷一個命令屬于 Shell 的內置命令還是程序,可以用 find 命令來判斷:如果 find 命令沒有任何響應,則表示該命令為 Shell 內置命令;如果顯示查找的結果,則該命令為 程序。 其實 Shell 的概念并不只存在與 Linux 系統,在其他的操作系統上也有,只不過名稱 不同,如 DOS 中的 command.com 和 Microsoft Windows 的 GUI(Graphical User Interface)。 但是 Linux 操作系統對于 Shell 極具靈活性的使用,是其他操作系統望塵莫及的。在 Linux 中可以使用的 Shell 很多,并且可以隨意更換不同的 Shell。 (2) Shell 的種類 Linux 支持的 Shell 都記錄在/etc/shells 文件中,我們可以使用 cat 命令來查看支持 的 Shell。 ``` [wd@localhost ~]$ cat /etc/shells /bin/sh /bin/bash /sbin/nologin /bin/tcsh /bin/csh /bin/ksh ``` 雖然每種 Unix/Linux 系統可以兼容的 Shell 有很多,但是使用較廣的只有三種: Bourne Shell(sh)、C Shell(csh)以及 Korn Shell(ksh)。 每種 Shell 的命令名稱和登錄時出現的提示符號不盡相同,表 3-5 是個簡單的說明。 表 3-5 常見 Shell 的說明 | Shell 名稱 | 命令名稱 | 登錄符號 | | --- | --- | --- | | Bourne | /bin/sh | $ | | C | /bin/csh | % | | Korn | ksh | $ | I\. Bourne Shell Bourne Shell 是最早被大量使用和標準化的 Shell,幾乎所有的 Unix/Linux 都支持。 由于 Bourne Shell 在執行效率上優于其他的 Shell,所以它是大多數 Unix 系統的默認Shell。但是它并不支持別名(aliases)與歷史記錄(history)等功能,同時在作業控制(Job Control)上的功能也比較簡單,所以整體而言,在目前的系統環境下已略顯不足。 II\. C Shell C Shell 的語法與 C 語言類似,它因此而得名。C Shell 已經是 Unix 類操作系統的重 要組成部分之一。 C Shell 的特點在于易于使用以及交互性強,目前 BSD 版的 UNIX 大多以 C Shell 作為 默認的 Shell。 III\. Korn Shell Korn Shell 兼具 Bourne Shell 和 C Shell 的優點,并且語法與 Bourne Shell 兼容, 但它出現的較晚,在一些新版的 Linux 發行版如 Ubuntu 中才有支持。 (3) 更改 Shell 因為 Linux 可以支持的 Shell 有很多,所以大家可以根據個人的習慣選擇使用不同的 Shell。要查看當前使用的 Shell 或系統默認的 Shell,最簡單的方式就是使用 echo 命令來 查詢系統的 Shell 環境變量,命令用法如下: ``` [wd@localhost ~]$ echo $SHELL /bin/bash #當前使用的 Shell 為 bash ``` 或者輸入: ``` [wd@localhost ~]$ echo ${SHELL} /bin/bash ``` 但是以上的方法只能顯示用戶登錄時使用的 Shell,而無法顯示出更換過的 Shell。如 果要更改使用的 Shell。只要執行該 Shell 程序名稱,即可切換到不同的 Shell。下面是更 改 Shell 的方法: ``` [wd@localhost ~]$ sudo sh #執行 sh $ sudo bash #執行 bash ``` 在一般情況下,執行 exit 命令會立即注銷系統。但如果從默認的 Shell 切換到其他的Shell,則不論切換的次數有多少,在切換后使用 exit 命令都不會注銷系統,而只會跳離 當前的 Shell,并回到上一層的 Shell。 以上切換 Shell 的方法雖然簡單,但它只是暫時的改變,待用戶注銷后登錄,又會回 到系統默認的 Shell。 要解決上述問題,可以使用 chsh 命令(Change Shell),它的使用方法很簡單。下面 是將用戶默認的 Shell 改為 csh 的例子: ``` [wd@localhost ~]$ sudo chsh 正在更改 root 的 Shell 請輸入新值,或直接敲回車鍵以使用默認值 登錄 Shell [/bin/bash]: /bin/csh # Shell 更改成功 ``` 注意,在使用 chsh 命令更改用戶默認的 Shell 后,要重新登錄才會更改。 2\.環境變量 所謂的“環境變量”(Environment Variables),是指 Shell 中用來保存系統信息的 變量,這些變量可供 Shell 中執行的程序使用。不同的 Shell 會有不同的環境變量名稱和 環境變量值的設置方法。 在 bash 中要顯示環境變量名稱及環境變量值,可以使用 set 命令。 3\.Shell 配置文件與 Shell Script 在上一小節里,我們介紹了環境變量的內容,并了解了如何自定義變量名稱或修改預 先定義的變量。而除了可以使用命令來執行變量的設置外,也可以通過一些 Shell 配置文 件來設置。在用戶登錄時,系統會檢查這些配置文件,以便設置環境。 本小節我們將列出所有與 Shell 有關的配置文件名稱,并且說明每個文件的功能。 (1) /etc/profile 這是系統最主要的 Shell 配置文件,也是用戶登錄時系統最先檢查的文件。系統最重 要的環境變量都定義在此,其中包括 PATH、USER、LOGNAME、MAIL、HOSTNAME、HISTSIZE 和 INPUTRC 等。 除此之外,這個文件也定義了 ulimit,它的功能是限制每個 Shell 所能執行的程序數 目,以免造成系統資源的過度消耗。而在文件的最后,它會檢查并執行 ``` /etc/profile.d/*.sh 的 Script。 ``` (2) ~/.bash_profile 這個文件是每位用戶的 BASH 環境配置文件,它存在于用戶的主目錄中。當系統執行 /etc/profile 后,就會接著讀取此文件內的設置值。 在此文件中,會定義 USERNAME、BASH_ENV 和 PATH 等環境變量。但是此處的 PATH 除了 包含系統的$PATH 變量外,還另外加入了用戶的 bin 目錄路徑,而 BASH_ENV 變量則指出接 下來系統要檢查的文件名稱。 (3) ~/.bashrc 接下來系統會檢查~/.bashrc 文件,這個文件和前兩個文件(/etc/profile 和 ~/.bash_profile)最大的不同是,每次執行 bash 時,~/.bashrc 都會被再次讀取,也就是 說變量會再次被設置;而/etc/profile 和~/.bash_profile 只有在登錄時才進行讀取。 就是因為經常被重新讀取,所以~/.bashrc 文件只用來定義一些終端設置及 Shell 提示 符號等,而不用來定義環境變量。 舉例來說,如果遠程的終端窗口(例如由微軟平臺以 Telnet 進行登錄)無法瀏覽超過 一頁的信息或文件內容,可以在此文件中加入下面這行: ``` export TERM=vt100 ``` ~/.bashrc 文件中值得注意的一行是“. /etc/bashrc”,它利用一個小數點接著一個空格鍵再指向另外一個 Script,表示同時執行此 Script,并且采用 Script 的變量設置。 (4) ~/.bash_login 如果~/.bash_profile 文件不存在,則系統會轉而讀取這個文件內容。這是用戶的登錄 文件,每次用戶登錄系統時,bash 都會讀取此文件,所以通常都會將登錄后必須執行的命 令放在這個文件中。 (5) ~/.profile 如果~/.bash_profile 和~/.bash_login 兩個文件都不存在,則會使用這個文件的設置 內容。它的功能與~/.bash_profile 完全相同。 (6) ~/.bash_logout 這個文件是 bash 在注銷系統前讀取的文件。通常這個文件只包含 clear 命令,也就是 先清除屏幕再注銷。如果想在注銷 Shell 前執行一些工作,例如清空緩沖區或執行備份, 都可以在此文件中設置。 (7) ~/.bash_history 這個文件中會記錄用戶曾經使用的命令歷史,以供查閱。 ### 3.3.6使用庫程序 編程庫是可以在多個軟件項目中重用的代碼集合。庫是軟件開發核心目標(代碼重用)的一個經典例子。它們把常用的編程例程和實用程序代碼收集到單獨位置。例如,標準的 C 庫包含數百個常用的例程,如輸出函數 printf()和輸入函數 getchar(),如果 每次創 建新程序都要重寫它們很讓人厭煩。但是,除了代碼重用和程序員獲得便利之外,庫還提供大量已完成調試和良好測試過的實用程序代碼,如用于網絡編程、圖像處理、數據操作和系統調用的例程。 在創建、維護以及管理編程庫時,需要知道可用的工具。有兩類庫:靜態的和共享的。 1\.靜態庫 靜態庫是包含目標文件的專門格式文件,這些文件稱為模塊或成員,是可重用的、預 編譯的代碼。以特殊格式將它們與一張表格或映射圖(把符號名鏈接到定義符號的成員)存 儲起來。映射圖可加快編譯和鏈接。靜態庫通常用擴展名 .a 命名,.a 代表歸檔(archive)。 2\.共享庫 又被稱作是動態庫。與靜態庫類似的地方是,共享庫也是文件,它包含其他目標文件或者指向其他目標文件的指針。稱它們為共享庫是因為在編譯程序時,不 需要將它們包含的代碼鏈接到程序。相反,動態的鏈接器 /裝載器在運行時把共享庫代碼鏈接到程序中。 與靜態庫相比,共享庫有幾個優勢。首先,它們需要的系統資源較少。因為共享庫代 碼沒有編譯成二進制代碼,而是在運行時從單個位置動態的鏈接和裝載,所以它們使用更少 的磁盤空間。它們使用的系統內存也較少,因為只需一次即可將它們裝載到內存。最后,共 享庫簡化了代碼和系統維護。修復 bug 或添加特性后,用戶只需獲得已更新的庫并且安裝 它即可。但對于靜態庫,必須重新編譯使用該庫的每個程序。 小貼士:如果想要構建一個在主機系統上不依靠任何內容的應用程序,或者在不確定開發 環境的地方使用特殊的命令時,使用靜態庫也有一定的優勢。但是通常建議使用共享庫。 3\.常見的庫命令 (1) nm 命令 nm 命令列出目標或二進制文件中所有已編碼的符號。使用它可查看程序調用了什么函 數,或者查看庫或目標文件是否提供了所需的函數。 nm 的語法如下: ``` nm [options] file ``` nm 列出存儲在 file 中的符號,該 file 必須是靜態庫或歸檔文件,正如前面小節所描述的。options 控制 nm 的行為。符號類似與在代碼中引用的函數、來自其他庫的全局變量 等內容。必須跟蹤找到程序所需的丟失符號時,可以使用 nm 命令作為一個工具。 表 3-6 介紹了有用的 nm 選項。 表 3-6 nm 命令行選項 | 選項 | 描述 | | --- | --- | | -c | 把符號名轉換成用戶級別名。這對使 C++函數名可讀特別有用 | | -l | 使用調試信息打印每個符號定義的行號,如果符號未定義,則重定位輸入項 | | -s | 在歸檔文件(.a)上使用時打印索引,該索引把符號名映射到定義符號的模塊或成員上 | | -u | 只把未定義的符號、外部定義的符號顯示到正被檢驗的文件 | 下面是一個例子,它使用 nm 顯示/usr/lib/libdl.a 中的一些符號: ``` $ nm /usr/lib/libdl.a | head dlopen.o: 0 0 0 0 0 0 4 0 T __dlopen_check U _dl_open U _dlerror_run 0 0 0 0 0 0 4 0 W dlopen 0 0 0 0 0 0 0 0 t dlopen_doit dlclose.o: U _dl_close ``` (2) ar 命令 ar 可創建、修改或者提取檔案。最常用于創建靜態庫(該庫是包含一個或者多個目標 文件的文件)。ar 還創建并維護一個表,該表交叉引用符號名和定義符號名的成員。 Ar 命 令的句法如下: ``` ar {dmpqrtx} [options] [member] archive file [...] ``` ar 根據 file 中列出的文件創建名為 archive 的檔案。至少要求使用 d、m、p、q、r、t 和 x 中的一個。通常使用 r。表 列出了最為常用的 ar 選項。 表 3-7 ar 命令行選項 | 選項 | 描述 | | --- | --- | | -c | 如果 archive 尚不存在,則通常發出禁止警告 | | -q | 不檢查是否有移位,把文件添加到 archive 末尾 | | -r | 把文件插入 archive,代替任何現有成員中其名字與正增加的 文件名匹配的成員。新成員增加到檔案末尾 | | -s | 創建或者更新映射,此映射將符號鏈接到定義符號的成員 | 小貼士:給定一個用 ar 命令創建的檔案,通過創建該檔案的索引,可以加快檔案的訪問速度。ranlib 可精確的完成該任務,它在檔案本身中存儲該索引。 Ranlib 的句法是: ``` ranlib [-v | -V] file ``` 這在 file 中生成符號映射,它等同于 ar -s file。 (3) ldd 命令 雖然 nm 可列出目標文件中定義的符號,但除非知道各個庫定義了哪些函數,否則它沒 什么太大用處。這就是 ldd 的工作了。它列出程序運行時所需的共享庫。它的句法是: ``` ldd [options] file ``` ldd 打印 file 要求的共享庫名。ldd 兩個最有用的選項是-d(它報告任何缺失的函數)和-r(它報告缺失的函數和缺失的數據對象)。例如,下面的 ldd 報告 ftp 客戶端 lftp(系統 上可能已安裝它,也可能未安裝)需要 13 個共享庫: ``` $ ldd /usr/bin/lftp liblftp-jobs.so.0 =&gt; /usr/lib/liblftp-jobs.so.0 (0x2aaf8000) liblftp-tasks.so.0 =&gt; /usr/lib/liblftp-tasks.so.0 (0x2ab48000) libreadline.so.5 =&gt; /usr/lib/libreadline.so.5 (0x2abbc000) libutil.so.1 =&gt; /lib/libutil.so.1 (0x2ac08000) libncurses.so.5 =&gt; /lib/libncurses.so.5 (0x2ac1c000) libresolv.so.2 =&gt; /lib/libresolv.so.2 (0x2ac50000) libdl.so.2 =&gt; /lib/libdl.so.2 (0x2ac78000) libc.so.6 =&gt; /lib/libc.so.6 (0x2ac8c000) libstdc++.so.6 =&gt; /usr/lib/libstdc++.so.6 (0x2ae24000) libm.so.6 =&gt; /lib/libm.so.6 (0x2af44000) libgcc_s.so.1 =&gt; /lib/libgcc_s.so.1 (0x2afdc000) /lib/ld.so.1 (0x2aaa8000) libtinfo.so.5 =&gt; /lib/libtinfo.so.5 (0x2b018000) ``` 具體系統上的輸出可能會有所不同。 (4) ldconfig 命令 ldconfig 確定共享庫所需的運行時鏈接,它位于 /usr/lib 和/lib 中,在命令行上 libs 中指定,存儲在/etc/ld.so.conf 中。它與動態鏈接器/裝載器 ld.so 一起工作,創建 并維護到系統上最新版本可用共享庫的鏈接。它的句法如下: ``` ldconfig [options] [libs] ``` 運行無參數的 ldconfig 只會更新緩存文件/etc/ld.so.cache。options 控制 ldconfig的行為。當 ldconfig 更新緩存時,-v 選項通知它是 verbose 的。-p 選項表示打印,但不 更新 ld.so 所知道的當前共享庫列表。要查看更新緩存時 ldconfig 正在完成什么,-v 選項可以打印顯示 ldconfig 已經找到的目錄和系統鏈接。 4\. 環境變量和配置文件 動態鏈接器/裝載器 ld.so 使用很多的環境變量來自定義和控制其行為。這些變量包括: ``` $LD_LIBRARY_PATH ``` 該變量包含一個用冒號分隔開的目錄列表,運行時在該表中查找共享庫。它類似于$PATH 環境變量。 ``` $LD_PRELOAD ``` 該變量是一個用空格分隔開的附加列表,其中包含用戶指定在所有其他庫之前裝載的庫。它用于有選擇的覆蓋其他共享庫中的函數。 ld.so 還使用兩個配置文件,其作用目的與那些環境變量平行: ``` /etc/ld.so.conf ``` 包含一個目錄列表,除了標準目錄(/usr/lib 和/lib,以及 64 位架構系統上的/lib64),連接器/裝載器還應該在該列表的目錄內搜索共享庫。 /etc/ld.so.preload 包含$LD_PRELOAD 環境變量基于磁盤的版本,它包括一個在執行程序之前裝載的、用空 格分開的共享庫列表。 可以借助$LD_PRELOAD 使用特定的版本來覆蓋已安裝的版本。在測試新的(或者不同 的)庫版本,而又不想在系統上安裝代替庫時,該功能通常很有用。通常在創建程序時只使 用環境變量。在生產環境中不要依賴這些環境變量,因為在過去它們導致過安全問題,所以 可能無法控制變量的值。 ### 3.3.3 使用 VI 一個常用的 Linux 開發工具箱中必須包含包括什么?基本上要包括一個編寫代碼的編 輯器、一個或者多個把源代碼轉化成二進制代碼的編譯器,以及一個跟蹤無法避免的 bug 的調試器。多數人都有喜愛的編輯器,試圖說服他們試用新的編輯器是很困難的事情。多數 編輯器支持一組與編程有關的功能(可以肯定有些支持的功能會更多)。可以使用的編輯器 太多,由于篇幅有限無法意義介紹,但有一點是必須要聲明的:至少需要一個編輯器。 只要使用 Linux,那么不使用文本編輯器幾乎是不可能的。這是因為多數 Linux 配置文 件是純文本文件,所以有時肯定需要進行手動修改。 如果正在使用 GUI,那么可以運行 gedit,編輯文本時使用它相當直觀。還有一個簡單 的文本編輯器 nano,可以從 shell 中運行它。但是多數 Linux Shell 用戶會使用 vi 或 emacs 命令來編輯文本文件。與圖形編輯器相比, vi 或 emacs 的優勢在于可以在任何 shell、字符終端或基于字符的網絡連接(例如使用 telnet 或 ssh)中使用它們,而無需使 用 GUI。它們都具有強大的功能,所以可以一直使用它們。 本節我們將提供一個簡單的 vi 文本編輯器教程,使用它可在任意 shell 中手動編輯配 置文件。 1\.運行 vi 通常情況下,運行 vi 可以打開特定的文件。例如,要打開 /tmp/test 文件,可輸入下 面的命令: ``` $ vi /tmp/test ``` 如果這是一個新文件,應該看到和下面類似的內容: ``` "/tmp/test" [New File] ``` 頂部的框表示光標的位置,底部的行通知編輯情況(此處只是打開了一個新文件)。 在這兩部分之間,波浪線(~)作為填充符,因為文件中還沒有任何文本。現在這是令人害 怕的部分:這里沒有提示、菜單或圖標告訴我們要做什么。不能只是從頂部開始輸入。如果 這樣做,計算機就會發出蜂鳴聲。所以有些人抱怨 Linux 并不友好。 (1) 首先需要了解的是不同的操作模式:命令或輸入。 vi 編輯器始終啟動到命令模式。在添加或修改文件中的文本前,必須輸入命令(一個 或者兩個字母加上一個可選的數字)告訴 vi 您想要做什么。大小寫很重要,所以要按例子 所示精確的使用大寫或小寫字母!要進入輸入模式,輸入該輸入命令。輸入下面的命令開始 操作。 + a:添加命令。在它之后,可以從光標的右端開始輸入文本。 + i:插入命令。在它之后,可以從光標的左端開始輸入文本。 輸入一些詞句,然后按下 Enter 鍵。重復執行該操作數次,直到有幾行文本為止。完成輸入后,按下 Esc 鍵反回到命令模式。現在文件中有些文本了,試用下面的鍵或字母在文本中移動。記住使用 Esc 鍵,它始終可以回到命令模式。 + 方向鍵:在文件中上、下、左或右移動光標,一次一個字符。也可以使用退格鍵和 空格鍵分別向左和向右移動。如果喜歡將手指放在鍵盤上,可使用 h(左)、l(右)、j(下)、 或 k(上)來移動 + w:將光標移動到下一個單詞的開頭。 + b:將光標移動到前個單詞的開頭。 + $(零):將光標移動到當前行的末尾。 + H:將光標移動到屏幕的左上角(屏幕上的第一行)。 + M:將光標移動到屏幕中間的第一個字符。 + L:將光標移動到屏幕的左下角(屏幕上的最后一行)。 (2) 其它編輯操作中唯一需要知道的是如何刪除文本。 下面是一些刪除文本用的命令。 + x:刪除光標下的字符。 + X:刪除光標前字符。 + dw:刪除從當前字符開始直到當前單詞末尾的所有字符。 + d$:刪除從當前字符開始直到當前行末尾的所有字符。 + d0:刪除從前一個字符開始直到當前行開頭的所有字符。 (3) 要結束編輯,可使用下列擊鍵保存和退出文件。 + ZZ:將當前修改保存到文件并退出 vi。 + :w:保存當前文件,但繼續編輯。 + :wq:與 ZZ 相同。 + :q:退出當前文件。沒有任何未保存的修改時該命令才會工作。 + :q!:退出當前文件,并且不保存對文件進行的修改。 小貼士:如果確實錯誤的修改了文件,那么 :q!命令是退出并且放棄修改的最好方法。文件會還原到最近修改的版本。所以如果只是使用 :w,有時可能會陷入困境。如果只想取消一些錯誤的編輯,按 u 鍵即可撤銷修改。 (4) 常用技巧 現在已經學習了一些 vi 編輯命令。在后面會介紹更多的命令。這里先列出首次使用 vi 的一些提示。 + Esc:記住,Esc 用于回到命令模式(我曾看到有人按下鍵盤上的所有鍵來嘗試退出 文件)。 + u:按 u 鍵可以撤銷之前做的修改。連續按 u 鍵可以撤銷更前面的修改。 + Ctrl+R:如果決定不再撤銷前面的命令,可使用 Ctrl+R 進行恢復。本質上,這個 命令取消所做的撤銷操作。 + Caps Lock:小心不要錯按了 Caps Lock 鍵。處于大寫狀態時,在 vi 中輸入的任何 內容都有不同含義。輸入大寫字母時不會出現警告,但事情卻開始變得不可思議。 + :!命令:在 vi 中,可使用:!后跟命令名的方式來運行命令。例如,輸入 :!date 查 看當前的時間和日期,輸入:!pwd 查看當前目錄,輸入:!jobs 查看后臺是否有任務正在運 行。命令運行完成時,按 Enter 鍵就可以返回繼續編輯文件。甚至可以使用該技術從 vi 中 啟動 shell(:!bash)、在該 shell 中運行幾個命令,然后鍵入 exit 返回到 vi(我建議 轉到 shell 前保存文件,防止回到 vi 后忘記保存)。 + —INSERT:處于插入模式時,INSERT 一詞會出現在屏幕底部。 + Ctrl+G:如果忘記了正在編輯的內容,按下這些鍵可在屏幕底部顯示正在編輯的文 件名和所在的行。它還顯示文件的總行數、己瀏覽過的部分占該文件的百分比,以及光標所 在的列號。這用來在下午停止工作一段時間后,幫助您確定編輯的位置。 2\.搜索文本 要搜索文本在文件中下次出現的位置,可使用斜線(/)或問號(?)。在斜線或問號后面加上模式(字符串或文本)可分別向前或向后搜索該模式。搜索時也可以使用元字符。下面是一些例子。 + /hello:向前搜索單詞 hello。 + ?goodbye:向后搜索單詞 goodbye。 + /The.*foot:向前搜索包括單詞 The,同時在 The 之后的某處有單詞 foot 的行。 + ?[pP]rint:向后搜索 pring 或 Print。記住,Linux 中是區分大小寫的,所以可使 用括號來搜索大小寫不同的單詞。 vi 編輯器最初基于 ex 編輯器,而 ex 編輯器不能完全在全屏幕模式下運行。但是它允 許運行命令,以便同時在一行或者多行中搜索和修改文本。輸入冒號并且光標到達屏幕底部 時,實際上就處于 ex 模式下。下面的例子用 ex 命令搜索和修改文本(例如我選擇搜索 Local 和 Remote,但也可以使用其它合適的單詞)。 + :g/Local:搜索單詞 Local,并且打印文件中它所出現的行(如果結果多于一個屏 幕,則以管道形式將輸出定向到 more 命令)。 + :s/Local/s//Remote:在當前行上用 Remote 代替 Local。 + :g/Local/s//Remote:用 Remote 代替文件中每行第一次出現的 Local。 + :g/Local/s//Remote/g:用 Remote 代替文件中出現的所有 Local。 + :g/Local/s//Remote/gp:用 Remote 代替文件中出現所有的 Local,然后打印沒一行 來查看進行的修改(如果輸出多于一頁,則以管道的形式將輸出定向到 more 命令)。 3\.使用命令和數字 在多數 vi 命令前都可以使用數字,這樣命令就能夠重復執行該指定數目的次數。這是 一次處理多行、多個單詞或多個字符的便捷方法。下面是一些例子。 + 3dw:刪除下面的 3 個單詞。 + 5cl:修改下面的 5 個字母(即刪除字母并進入輸入模式)。 + 12j:向下移動 12 行。 在多數命令前加上數字只是重復執行這些命令。此時對于使用 vi 命令應該相當精通了。一旦習慣了使用 vi,就會發現其它文本編輯器使用起來效率都不高了。 在很多 Linux 系統中調用 vi 時,實際上正在調用 vim 文本編輯器,它運行在 vi 兼容 模式下。進行大量編程工作的人可能更愿意使用 vim,因為它以不同顏色顯示不同的代碼層 次。vim 還有一些其它有用的功能,例如在打開文檔時,將光標放在最后一次退出文件時光 標所在的位置。 vi 編輯器在開始時很難學,可是一旦掌握了它,就永遠不必使用鼠標或功能鍵了-一 個鍵盤就可以快速高效的在文件中編輯和移動。 ### 3.3.4 使用 GCC 1\.GCC 簡介 通常所說的 GCC 是 GUN Compiler Collection 的簡稱,除了編譯程序之外,它還含其 他相關工具,所以它能把易于人類使用的高級語言編寫的源代碼構建成計算機能夠直接執行 的二進制代碼。GCC 是 Linux 平臺下最常用的編譯程序,它是 Linux 平臺編譯器的事實標準。同時,在 Linux 平臺下的嵌入式開發領域,GCC 也是用得最普遍的一種編譯器。GCC 之 所以被廣泛采用,是因為它能支持各種不同的目標體系結構。例如,它既支持基于宿主的開 發(簡單講就是要為某平臺編譯程序,就在該平臺上編譯),也支持交叉編譯(即在 A 平臺上編譯的程序是供平臺 B 使用的)。目前,GCC 支持的體系結構有四十余種,常見的有 X86 系列、Arm、 PowerPC 等。同時,GCC 還能運行在不同的操作系統上,如 Linux、 Solaris、Windows 等。 除了上面講的之外,GCC 除了支持 C 語言外,還支持多種其他語言,例如 C++、Ada、 Java、Objective-C、FORTRAN、Pascal 等。 下面我們將介紹 Linux 平臺下應用程序的編譯過程,以及使用 GCC 編譯應用程序的具 體用法,同時詳細說明了 GCC 的常用選項、模式和警告選項。 2\.使用 GCC 編譯程序的過程 對于 GNU 通用編譯器來說,程序的編譯要經歷預處理、編譯、匯編、連接四個階段, 如下圖所示: 從功能上分,預處理、編譯、匯編是三個不同的階段,但 GCC 的實際操作上,它可以 把這三個步驟合并為一個步驟來執行。下面我們以 C 語言為例來談一下不同階段的輸入和 輸出情況。 在預處理階段,輸入的是 C 語言的源文件,通常為*.c。它們通常帶有.h 之類頭文件的 包含文件。這個階段主要處理源文件中的 #ifdef、 #include 和#define 命令。該階段會生 成一個中間文件*.i,但實際工作中通常不用專門生成這種文件,因為基本上用不到;若非 要生成這種文件不可,可以利用下面的示例命令: ``` GCC -E test.c -o test.i ``` 在編譯階段,輸入的是中間文件*.i,編譯后生成匯編語言文件*.s 。這個階段對應的GCC 命令如下所示: ``` GCC -S test.i -o test.s ``` 在匯編階段,將輸入的匯編文件*.s 轉換成機器語言*.o。這個階段對應的 GCC 命令如下所示: ``` GCC -c test.s -o test.o ``` 最后,在連接階段將輸入的機器代碼文件 *.s(與其它的機器代碼文件和庫文件)匯集成一個可執行的二進制代碼文件。這一步驟,可以利用下面的示例命令完成: ``` GCC test.o -o test ``` 上面介紹了 GCC 編譯過程的四個階段以及相應的命令。下面我們進一步介紹常用 GCC的模式。 3\.GCC 常用模式 這里介紹 GCC 追常用的兩種模式:編譯模式和編譯連接模式。下面以一個例子來說明 各種模式的使用方法。為簡單起見,假設我們全部的源代碼都在一個文件 test.c 中,要想 把這個源文件直接編譯成可執行程序,可以使用以下命令: ``` $ GCC -o test ``` 這里 test.c 是源文件,生成的可執行代碼存放在一個名為 test 的文件中(該文件是 機器代碼并且可執行)。-o 是生成可執行文件的輸出選項。如果我們只想讓源文件生成目 標文件(給文件雖然也是機器代碼但不可執行),可以使用標記 -c ,詳細命令如下所示: ``` $ GCC -c test.c ``` 默認情況下,生成的目標文件被命名為 test.o,但我們也可以為輸出文件指定名稱, 如下所示: ``` $ GCC -c test.c -o ``` 上面這條命令將編譯后的目標文件命名為 mytest.o,而不是默認的 test.o。 迄今為止,我們談論的程序僅涉及到一個源文件;現實中,一個程序的源代碼通常包 含在多個源文件之中,這該怎么辦?沒關系,即使這樣,用 GCC 處理起來也并不復雜,見 下例: ``` $ GCC -o test first.c second.c third.c ``` 該命令將同時編譯 3 個源文件,即 first.c、second.c 和 third.c,然后將它們連接成一個可執行程序,名為 test。 許多情況下,頭文件和源文件會單獨存放在不同的目錄中。例如,假設存放源文件的 子目錄名為./src,而包含文件則放在層次的其他目錄下,如 ./inc。當我們在./src 目錄下 進行編譯工作時,如何告訴 GCC 到哪里找頭文件呢?方法如下所示: ``` $ gcc test.c –I../inc -o test ``` 上面的命令告訴 GCC 包含文件存放在./inc 目錄下,在當前目錄的上一級。如果在編譯時需要的包含文件存放在多個目錄下,可以使用多個 -I 來指定各個目錄: ``` $ gcc test.c –I../inc –I../../inc2 -o test ``` 這里指出了另一個包含子目錄 inc2,較之前目錄它還要在再上兩級才能找到。 另外,我們還可以在編譯命令行中定義符號常量。為此,我們可以簡單的在命令行中 使用-D 選項即可,如下例所示: ``` $ gcc -DTEST_CONFIGURATION test.c -o test ``` 上面的命令與在源文件中加入下列命令是等效的: ``` #define TEST_CONFIGURATION ``` 在編譯命令行中定義符號常量的好處是,不必修改源文件就能改變由符號常量控制的行為。 實際上,GCC 命令提供了非常多的命令選項,但并不是所有都要熟悉,初學時掌握幾個常用的就可以了,到后面再慢慢學習其它選項,免得因選項太多而打擊了學習的信心。 為了方便讀者查閱,這里將常見的編譯選項列舉如下: (1) 常用編譯命令選項 假設源程序文件名為 test.c。 I\. 無選項編譯鏈接 用法:#gcc test.c 作用:將 test.c 預處理、匯編、編譯并鏈接形成可執行文件。這里未指定輸出文件, 默認輸出為 a.out。 II\. 選項 -o 用法:#gcc test.c -o test 作用:將 test.c 預處理、匯編、編譯并鏈接形成可執行文件 test。-o 選項用來指定 輸出文件的文件名。 III\. 選項 -E 用法:#gcc -E test.c -o test.i 作用:將 test.c 預處理輸出 test.i 文件。 IV. 選項 -S 用法:#gcc -S test.i 作用:將預處理輸出文件 test.i 匯編成 test.s 文件。 V. 選項 -c 用法:#gcc -c test.s 作用:將匯編輸出文件 test.s 編譯輸出 test.o 文件。 VI. 無選項鏈接 用法:#gcc test.o -o test 作用:將編譯輸出文件 test.o 鏈接成最終可執行文件 test。 VII. 選項-O 用法:#gcc -O1 test.c -o test 作用:使用編譯優化級別 1 編譯程序。級別為 1~3,級別越大優化效果越好,但編譯時 間越長。 (2) 多源文件的編譯方法 如果有多個源文件,基本上有兩種編譯方法 (這里假設有兩個源文件為 test.c 和 testfun.c。)。 I\. 多個文件一起編譯 用法:#gcc testfun.c test.c -o test 作用:將 testfun.c 和 test.c 分別編譯后鏈接成 test 可執行文件。 II\. 分別編譯各個源文件,之后對編譯后輸出的目標文件鏈接。 用法: ``` #gcc -c testfun.c //將 testfun.c 編譯成 testfun.o #gcc -c test.c //將 test.c 編譯成 test.o #gcc -o testfun.o test.o -o test //將 testfun.o 和 test.o 鏈接成 test ``` 以上兩種方法相比較,第 1 種方法編譯時需要所有文件重新編譯,而第 2 種方法可以只重新編譯修改的文件,未修改的文件不用重新編譯。 4\.警告功能 當 GCC 在編譯過程中檢查出錯誤的話,它就會中止編譯;但檢測到警告時卻能繼續編 譯生成可執行程序,因為警告只是針對程序結構的診斷信息,它不能說明程序一定有錯誤, 而是存在風險,或者可能存在錯誤。雖然 GCC 提供了非常豐富的警告,但前提是你已經啟 用了它們,否則它不會報告這些檢測到的警告。 在眾多的警告選項之中,最常用的就是 -Wall 選項。該選項能發現程序中一系列的常見 錯誤警告,該選項用法舉例如下: ``` $ gcc -Wall test.c -o test ``` 該選項相當于同時使用了下列所有的選項: + unused-function:遇到僅聲明過但尚未定義的靜態函數時發出警告。 + unused-label:遇到聲明過但不使用的標號的警告。 + unused-parameter:從未用過的函數參數的警告。 + unused-variable:在本地聲明但從未用過的變量的警告。 + unused-value:僅計算但從未用過的值得警告。 + Format:檢查對 printf 和 scanf 等函數的調用,確認各個參數類型和格式串中的 一致。 + implicit-int:警告沒有規定類型的聲明。 + implicit-function-:在函數在未經聲明就使用時給予警告。 + char-subscripts:警告把 char 類型作為數組下標。這是常見錯誤,程序員經常忘記在某些機器上 char 有符號。 + missing-braces:聚合初始化兩邊缺少大括號。 + Parentheses:在某些情況下如果忽略了括號,編譯器就發出警告。 + return-type:如果函數定義了返回類型,而默認類型是 int 型,編譯器就發出警 告。同時警告那些不帶返回值的 return 語句,如果他們所屬的函數并非 void 類型。 + sequence-point:出現可疑的代碼元素時,發出報警。 + Switch:如果某條 switch 語句的參數屬于枚舉類型,但是沒有對應的 case 語句使 用枚舉元素,編譯器就發出警告(在 switch 語句中使用 default 分支能夠防止這個警 告)。超出枚舉范圍的 case 語句同樣會導致這個警告。 + strict-aliasing:對變量別名進行最嚴格的檢查。 + unknown-pragmas:使用了不允許的#pragma。 + Uninitialized:在初始化之前就使用自動變量。 需要注意的是,各警告選項既然能使之生效,當然也能使之關閉。比如假設我們想要使用-Wall 來啟用個選項,同時又要關閉 unused 警告,可以通過下面的命令來達到目的: ``` $ gcc -Wall -Wno-unused test.c -o test ``` 下面是使用-Wall 選項的時候沒有生效的一些警告項: + cast-align:一旦某個指針類型強制轉換時,會導致目標所需的地址對齊邊界擴 展,編譯器就發出警告。例如,某些機器上只能在 2 或 4 字節邊界上訪問整數,如果在這 種機型上把 char *強制轉換成 int *類型, 編譯器就發出警告。 + sign-compare:將有符號類型和無符號類型數據進行比較時發出警告。 + missing-prototypes :如果沒有預先聲明函數原形就定義了全局函數,編譯器就 發出警告。即使函數定義自身提供了函數原形也會產生這個警告。這樣做的目的是檢查沒有 在頭文件中聲明的全局函數。 + Packed:當結構體帶有 packed 屬性但實際并沒有出現緊縮式給出警告。 + Padded:如果結構體通過充填進行對齊則給出警告。 + unreachable-code:如果發現從未執行的代碼時給出警告。 + Inline:如果某函數不能內嵌(inline),無論是聲明為 inline 或者是指定了- finline-functions 選項,編譯器都將發出警告。 + disabled-optimization:當需要太長時間或過多資源而導致不能完成某項優化時 給出警告。 上面是使用-Wall 選項時沒有生效,但又比較常用的一些警告選項。本文中要介紹的最后一個常用警告選項是-Werror。使用該選項后,GCC 發現可疑之處時不會簡單的發出警告就算完事,而是將警告作為一個錯誤而中斷編譯過程。該選項在希望得到高質量代碼時非常 有用。 ### 3.3.5 使用 GDB 1\.概述 GDB 是 GNU 開源組織發布的在 Linux 系統下用來調試 C 和 C++程序的強力調試器,它 可以在程序運行時用來觀察程序的內部結構和內存等的使用情況。 一般來說,GDB 主要幫助程序員完成以下 4 個方面的工作: (1) 啟動程序,可以按照自定義的要求運行程序; (2) 在被調試的程序所指定的斷點處停止; (3) 程序停止時檢查此時程序中所發生的事件; (4) 動態的改變程序執行環境。 在命令行上輸入“gdb”并按回車鍵就可以運行 GDB 了,如果一切正常的話 GDB 將被啟 動并且將在屏幕上顯示如下內容: ``` #gdb xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ``` 當啟動 GDB 后,在命令行上可以指定很多的選項,也可以下面的形式來運行 GDB: ``` GDB &lt;程序名&gt; ``` 當用這種方式運行 GDB 時,能直接指定想要調試的程序,GDB 將裝入某個可執行文件。 GDB 也可以檢查一個因程序異常終止而產生的 core 文件。 為了使 GDB 正常工作,必須使程序在編譯時包含調試信息。調試信息包含程序里的每 個變量的類型和在可執行文件里面的地址映射以及源代碼的行號, GDB 利用這些信息使源代 碼和機器碼相關聯。在編譯用“-g”選項打開調試選項。 2\.GDB 基本命令 GDB 支持很多的命令,能實現不同的功能。表 2-1 列出了 GDB 調試時常用的一些命令。 表 3-8 GDB 基本命令 | 命令 | 含義 | | --- | --- | | backtrace | 顯示函數調用的所有棧框架的蹤跡和當前函數的參數值 | | break | 設置一個斷點,這個命令需要指定代碼行或者函數名作為參數 | | clear | 刪除一個斷點,這個命令需要指定代碼行或者函數名作為參數 | | continue | 在調試器停止的地方繼續執行 | | Ctrl+C | 在當前位置停止執行正在執行的程序,斷點在當前行 | | disable | 禁止斷點功能,這個命令需要禁止的斷點在斷點列表索引值作為參數 | | display | 在斷點停止的地方顯式指定的表達式的值 | | enable | 允許斷點功能,這個命令需要禁止的斷點在斷點列表索引值作為參數 | | finish | 繼續執行,直到當前函數返回 | | ignore | 忽略某個斷點制定的次數。例如:“ignore4 23”忽略斷點 4 的 23 次運行,在第 24 此運行時中斷 | | Info breakpoints | 查看斷點信息 | | Info display | 查看設置的需要顯式的表達式的信息 | | kill | 終止當前 debug 進程 | | list | 顯示 10 行代碼。如果沒有提供參數給這個命令,則從當前行開始顯示 10 行代碼。如果提供了函數名作為參數,則從函數開頭顯示。如果提供代碼行的編號作為參數,這一行作為開頭顯示 | | load | 動態載入一個可執行文件到調試器 | | next | 執行下一行的源代碼的所有指令。如果是函數調用,則也當作一行源代碼,執行到此函數返回 | | nexti | 執行下一行源代碼中的一條匯編指令 | | print | 顯式變量的值 | | ptype | 顯示變量的類型 | | return | 強制從當前函數返回 | | run | 從程序開始的地方執行 | | rwatch | 指定一個變量,如果這個變量被讀,則暫停程序運行,在調試器中顯示信息,并等待下一個調試指令,參考 rwatch 和 watch 命令 | | set | 設置變量的值。例如:“set nval=54”將把 54 保存到 nval 變量中 | | step | 繼續執行程序下一行源代碼的所有指令。如果是調用函數,這個命令將進入函數的內部,單步執行函數中的代碼 | | stepi | 繼續執行程序下一行源代碼的匯編指令。如果是調用函數,這個命令將進入函數的內部,單步執行函數中的代碼 | | txbreak | 在當前函數的退出點上設置一個臨時斷點(只可使用一次) | | undisplay | 刪除一個 display 設置的變量顯示,這個命令需要將 display list 中的索引作為參數 | | watch | 指定一個變量,如果這個變量被寫,則暫停程序運行,在調試器中顯示信息,并等待下一個調試命令,參考 rwatch 和 watch 命令 | | whatis | 顯示變量的值和類型 | | xbreak | 在當前函數的退出點上設置一個斷點 | | awatch | 指定一個變量,如果這個變量被讀寫,則暫停程序運行,在調試器中顯示信息,并等待下一個調試命令,參考 rwatch 和 watch 命令 | 3\.GDB 的操作 GDB 支持很多與 shell 程序一樣的命令編輯特征。能像在 Bash 里那樣按“Tab”鍵讓 GDB 補齊一個唯一的命令。如果該命令不唯一的話, GDB 會列出所有匹配的命令,也能用光 標鍵上下翻動歷史命令。更為詳盡的內容,請查閱 GDB 的幫助。 4\.GDB 應用舉例 本小節通過一個實例一步步的用 GDB 調試程序。下面是將被調試的程序,這個程序被 稱為 greeting.c,它顯示一個簡單的問候,再用反序將它列出。 ``` #include &lt;stdio.h&gt; #filename.greeting.c main() { char my_string[] = "hello there"; my_print (my_string); my_print2 (my_string); } void my_print(char *string) { printf("The string is %s\n",string); } void my_print2(char *string) { char *string2; int size,i; size = strlen(string); string2 = (char *) malloc(size+1); for ( i=0;i&lt;size;i++) { string2[size -i] = string[i]; string2[size+1] = '\0'; printf("The string printed backward is %s\n",string2); } } ``` 用命令編譯它: ``` #gcc -o test test.c ``` 這個程序執行時顯示如下結果: ``` The string is hello there The string printed backward is ``` 輸出的第一行是正確的,但第二行打印出來的東西并不是所期望的,所設想的輸出應該是: ``` The string printed backward is ereht olleh ``` 由于某些原因,my_print2 函數沒有正常工作。用 GDB 查看問題究竟出在哪兒,先輸入如下命令: ``` #gdb greeting ``` 注意,在編譯 greeting 程序時需要把調試程序打開 如果在輸入命令時忘了把要調試的程序作為參數傳遞給 GDB,可以在 GDB 提示符下用 file 命令載入它: ``` (gdb) file greeting ``` 這個命令將載入 greeting 可執行文件,就像在 GDB 命令行里裝入它一樣。 這時可以用 GDB 的 run 命令運行 greeting 了,其在 GDB 里被運行后結果如下: ``` (gdb) run Starting program: /root/geeeting The string is hello there The string printed backward is Program exited with code p41 ``` 這個輸出和在 GDB 外面運行的結果一樣,但為什么反序打印卻沒有工作呢?為了找出癥結所在,可以在 my_pringt2 函數的 for 語句后設一個斷點,具體的做法是在 gdb 提示符 下鍵入 list 命令 3 次,列出源代碼,如下所示: ``` (gdb) list (gdb) list (gdb) list ``` 第一次輸入 list 命令的輸出如下: ``` #include &lt;stdio.h&gt; main() { char my_string[] = "hello there"; my_print(my_string); my_print2(my_string); } ``` 如果按下回車,GDB 將再執行一次 list 命令,輸出如下: ``` my_print(char *string) { printf("The string is %s\n",string); } my_print2(char *string) { char *string2; Int size,i; } ``` 再按一次一次回車將列出 greeting 程序的剩余部分,如下所示: ``` size = strlen(strint); string2 = (char*)malloc(size+1); for(i=0;i&lt;size;i++) { string2[size-i] = string[i]; string2[size+1] = '\0'; printf("The string printed backward is %s\n",string2); } ``` 從列出的源程序可以看到要設斷點的地方在第 24 行,在 GDB 命令行提示符下輸入如下命令設置斷點: ``` (gdb) break 24 ``` GDB 將作出如下的響應: ``` Breakpoint 1 at 0x139: file greeting.c,line24 (gdb) ``` 現在再輸入 run 命令,將產生如下的輸出: ``` Starting program:/root/greeting The string is hello there Breakpoint 1,my_print2(string = 0xbfffdc4 "hello there") at greeting.c :24 24 string2[size-i] = string[i]; ``` 能通過設置一個觀察 string2[size-i]變量值的觀察點來查看錯誤是怎樣產生的,輸入一下指令: ``` (gdb) watch string2[size-i]; ``` GDB 將作出如下回應: ``` Watchpoint 2:string2[size-i] ``` 現在可以用 next 命令一步一步的執行 for 循環了,如下所示: ``` (gdb) next ``` 經過第一次循環后:GDB 告訴我們 string2[size-i]的值是 h。GDB 用如下的顯示來告訴這個信息: ``` Watchpoint 2,string2[size-i] Old value=0 '\000' New value = 104 'h' my_print2(string = 0xbfffdc4 "hello there") at greeting.c:23 23 for ( i= 0;i&lt;size; i++ ) ``` 這個值正是期望的,后來的數次循環的結果都是正確的。當 i=10 時,表達式string2[size-i]的值等于 e,size-i 的值等于 1,最后 1 個字符已經復制到字符串里了。 如果再把循環執行下去,會看到已經沒有值分配給 string2[0]了,而它是字符串的第 1 個字符,因為 malloc()函數在分配內存時把它們初始化為空 (null)字符,所以 string2 的第 1 個字符是空字符,這就解釋了打印 string2 時沒有任何輸出的原因。 現在找出了問題在哪里,改正這個錯誤很容易。需要把代碼里寫入 string2 的第 1 個 字符的偏移量改為 size-1。 使代碼正常工作有很多種修改方法。一種是另設一個比實際大小小 1 的變量,這種解 決辦法的代碼如下: ``` #include &lt;stdio.h&gt; main() { char my_string[] = "hello there"; my_print(my_string); my_print2(my_string); } my_print(char *string) { printf("The string is %s\n",string); } my_print2(char *string) { char *string2; int size,size2,i; size = strlen(string); size2 = size-1; string2 = (char*)malloc(size+1); for( i = 0;i&lt;size;i++ ) { string2[size-i] = string[i]; } string2[size] = '\0'; printf("The string printed backward is %s\n",string2); } ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看