Linux操作系統中GCC的應用介紹(上)
在為L(cháng)inux開(kāi)發(fā)應用程序時(shí),絕大多數情況下使用的都是C語(yǔ)言,因此幾乎每一位Linux程序員面臨的首要問(wèn)題都是如何靈活運用C編譯器。目前Linux 下最常用的C語(yǔ)言編譯器是GCC(GNU Compiler Collection),它是GNU項目中符合ANSI C標準的編譯系統,能夠編譯用C、C++和Object C等語(yǔ)言編寫(xiě)的程序。GCC不僅功能非常強大,結構也異常靈活。最值得稱(chēng)道的一點(diǎn)就是它可以通過(guò)不同的前端模塊來(lái)支持各種語(yǔ)言,如Java、 Fortran、Pascal、Modula-3和Ada等。
開(kāi)放、自由和靈活是Linux的魅力所在,而這一點(diǎn)在GCC上的體現就是程序員通過(guò)它能夠更好地控制整個(gè)編譯過(guò)程。在使用GCC編譯程序時(shí),編譯過(guò)程可以被細分為四個(gè)階段:
◆ 預處理(Pre-Processing)
◆ 編譯(Compiling)
◆ 匯編(Assembling)
◆ 鏈接(Linking)
Linux程序員可以根據自己的需要讓 GCC在編譯的任何階段結束,以便檢查或使用編譯器在該階段的輸出信息,或者對最后生成的二進(jìn)制文件進(jìn)行控制,以便通過(guò)加入不同數量和種類(lèi)的調試代碼來(lái)為今后的調試做好準備。和其它常用的編譯器一樣,GCC也提供了靈活而強大的代碼優(yōu)化功能,利用它可以生成執行效率更高的代碼。
GCC提供了30多條警告信息和三個(gè)警告級別,使用它們有助于增強程序的穩定性和可移植性。此外,GCC還對標準的C和C++語(yǔ)言進(jìn)行了大量的擴展,提高程序的執行效率,有助于編譯器進(jìn)行代碼優(yōu)化,能夠減輕編程的工作量。
GCC起步
在學(xué)習使用GCC之前,下面的這個(gè)例子能夠幫助用戶(hù)迅速理解GCC的工作原理,并將其立即運用到實(shí)際的項目開(kāi)發(fā)中去。首先用熟悉的編輯器輸入清單1所示的代碼:
清單1:hello.c
|
然后執行下面的命令編譯和運行這段程序:
|
從程序員的角度看,只需簡(jiǎn)單地執行一條GCC命令就可以了,但從編譯器的角度來(lái)看,卻需要完成一系列非常繁雜的工作。首先,GCC需要調用預處理程序 cpp,由它負責展開(kāi)在源文件中定義的宏,并向其中插入“#include”語(yǔ)句所包含的內容;接著(zhù),GCC會(huì )調用ccl和as將處理后的源代碼編譯成目標代碼;最后,GCC會(huì )調用鏈接程序ld,把生成的目標代碼鏈接成一個(gè)可執行程序。
為了更好地理解GCC的工作過(guò)程,可以把上述編譯過(guò)程分成幾個(gè)步驟單獨進(jìn)行,并觀(guān)察每步的運行結果。第一步是進(jìn)行預編譯,使用-E參數可以讓GCC在預處理結束后停止編譯過(guò)程:
|
此時(shí)若查看hello.cpp文件中的內容,會(huì )發(fā)現stdio.h的內容確實(shí)都插到文件里去了,而其它應當被預處理的宏定義也都做了相應的處理。下一步是將hello.i編譯為目標代碼,這可以通過(guò)使用-c參數來(lái)完成:
|
GCC默認將.i文件看成是預處理后的C語(yǔ)言源代碼,因此上述命令將自動(dòng)跳過(guò)預處理步驟而開(kāi)始執行編譯過(guò)程,也可以使用-x參數讓GCC從指定的步驟開(kāi)始編譯。最后一步是將生成的目標文件鏈接成可執行文件:
|
在為L(cháng)inux開(kāi)發(fā)應用程序時(shí),絕大多數情況下使用的都是C語(yǔ)言,因此幾乎每一位Linux程序員面臨的首要問(wèn)題都是如何靈活運用C編譯器。目前Linux 下最常用的C語(yǔ)言編譯器是GCC(GNU Compiler Collection),它是GNU項目中符合ANSI C標準的編譯系統,能夠編譯用C、C++和Object C等語(yǔ)言編寫(xiě)的程序。GCC不僅功能非常強大,結構也異常靈活。最值得稱(chēng)道的一點(diǎn)就是它可以通過(guò)不同的前端模塊來(lái)支持各種語(yǔ)言,如Java、 Fortran、Pascal、Modula-3和Ada等。
{{分頁(yè)}}
開(kāi)放、自由和靈活是Linux的魅力所在,而這一點(diǎn)在GCC上的體現就是程序員通過(guò)它能夠更好地控制整個(gè)編譯過(guò)程。在使用GCC編譯程序時(shí),編譯過(guò)程可以被細分為四個(gè)階段:
◆ 預處理(Pre-Processing)
◆ 編譯(Compiling)
◆ 匯編(Assembling)
◆ 鏈接(Linking)
Linux程序員可以根據自己的需要讓 GCC在編譯的任何階段結束,以便檢查或使用編譯器在該階段的輸出信息,或者對最后生成的二進(jìn)制文件進(jìn)行控制,以便通過(guò)加入不同數量和種類(lèi)的調試代碼來(lái)為今后的調試做好準備。和其它常用的編譯器一樣,GCC也提供了靈活而強大的代碼優(yōu)化功能,利用它可以生成執行效率更高的代碼。
GCC提供了30多條警告信息和三個(gè)警告級別,使用它們有助于增強程序的穩定性和可移植性。此外,GCC還對標準的C和C++語(yǔ)言進(jìn)行了大量的擴展,提高程序的執行效率,有助于編譯器進(jìn)行代碼優(yōu)化,能夠減輕編程的工作量。
GCC起步
在學(xué)習使用GCC之前,下面的這個(gè)例子能夠幫助用戶(hù)迅速理解GCC的工作原理,并將其立即運用到實(shí)際的項目開(kāi)發(fā)中去。首先用熟悉的編輯器輸入清單1所示的代碼:
清單1:hello.c
|
然后執行下面的命令編譯和運行這段程序:
|
從程序員的角度看,只需簡(jiǎn)單地執行一條GCC命令就可以了,但從編譯器的角度來(lái)看,卻需要完成一系列非常繁雜的工作。首先,GCC需要調用預處理程序 cpp,由它負責展開(kāi)在源文件中定義的宏,并向其中插入“#include”語(yǔ)句所包含的內容;接著(zhù),GCC會(huì )調用ccl和as將處理后的源代碼編譯成目標代碼;最后,GCC會(huì )調用鏈接程序ld,把生成的目標代碼鏈接成一個(gè)可執行程序。
為了更好地理解GCC的工作過(guò)程,可以把上述編譯過(guò)程分成幾個(gè)步驟單獨進(jìn)行,并觀(guān)察每步的運行結果。第一步是進(jìn)行預編譯,使用-E參數可以讓GCC在預處理結束后停止編譯過(guò)程:
|
此時(shí)若查看hello.cpp文件中的內容,會(huì )發(fā)現stdio.h的內容確實(shí)都插到文件里去了,而其它應當被預處理的宏定義也都做了相應的處理。下一步是將hello.i編譯為目標代碼,這可以通過(guò)使用-c參數來(lái)完成:
|
GCC默認將.i文件看成是預處理后的C語(yǔ)言源代碼,因此上述命令將自動(dòng)跳過(guò)預處理步驟而開(kāi)始執行編譯過(guò)程,也可以使用-x參數讓GCC從指定的步驟開(kāi)始編譯。最后一步是將生成的目標文件鏈接成可執行文件:
|
在采用模塊化的設計思想進(jìn)行軟件開(kāi)發(fā)時(shí),通常整個(gè)程序是由多個(gè)源文件組成的,相應地也就形成了多個(gè)編譯單元,使用GCC能夠很好地管理這些編譯單元。假設有一個(gè)由foo1.c和foo2.c兩個(gè)源文件組成的程序,為了對它們進(jìn)行編譯,并最終生成可執行程序foo,可以使用下面這條命令:
|
如果同時(shí)處理的文件不止一個(gè),GCC仍然會(huì )按照預處理、編譯和鏈接的過(guò)程依次進(jìn)行。如果深究起來(lái),上面這條命令大致相當于依次執行如下三條命令:
|
在編譯一個(gè)包含許多源文件的工程時(shí),若只用一條GCC命令來(lái)完成編譯是非常浪費時(shí)間的。假設項目中有100個(gè)源文件需要編譯,并且每個(gè)源文件中都包含 10000行代碼,如果像上面那樣僅用一條GCC命令來(lái)完成編譯工作,那么GCC需要將每個(gè)源文件都重新編譯一遍,然后再全部連接起來(lái)。很顯然,這樣浪費的時(shí)間相當多,尤其是當用戶(hù)只是修改了其中某一個(gè)文件的時(shí)候,完全沒(méi)有必要將每個(gè)文件都重新編譯一遍,因為很多已經(jīng)生成的目標文件是不會(huì )改變的。要解決這個(gè)問(wèn)題,關(guān)鍵是要靈活運用GCC,同時(shí)還要借助像Make這樣的工具。
警告提示功能
GCC包含完整的出錯檢查和警告提示功能,它們可以幫助Linux程序員寫(xiě)出更加專(zhuān)業(yè)和優(yōu)美的代碼。先來(lái)讀讀清單2所示的程序,這段代碼寫(xiě)得很糟糕,仔細檢查一下不難挑出很多毛?。?
{{分頁(yè)}}
◆main函數的返回值被聲明為void,但實(shí)際上應該是int;
◆使用了GNU語(yǔ)法擴展,即使用long long來(lái)聲明64位整數,不符合ANSI/ISO C語(yǔ)言標準;
◆main函數在終止前沒(méi)有調用return語(yǔ)句。
清單2:illcode.c
|
下面來(lái)看看GCC是如何幫助程序員來(lái)發(fā)現這些錯誤的。當GCC在編譯不符合ANSI/ISO C語(yǔ)言標準的源代碼時(shí),如果加上了-pedantic選項,那么使用了擴展語(yǔ)法的地方將產(chǎn)生相應的警告信息:
|
需要注意的是,-pedantic編譯選項并不能保證被編譯程序與ANSI/ISO C標準的完全兼容,它僅僅只能用來(lái)幫助Linux程序員離這個(gè)目標越來(lái)越近?;蛘邠Q句話(huà)說(shuō),-pedantic選項能夠幫助程序員發(fā)現一些不符合ANSI/ISO C標準的代碼,但不是全部,事實(shí)上只有ANSI/ISO C語(yǔ)言標準中要求進(jìn)行編譯器診斷的那些情況,才有可能被GCC發(fā)現并提出警告。
除了-pedantic之外,GCC還有一些其它編譯選項也能夠產(chǎn)生有用的警告信息。這些選項大多以-W開(kāi)頭,其中最有價(jià)值的當數-Wall了,使用它能夠使GCC產(chǎn)生盡可能多的警告信息:
|
GCC給出的警告信息雖然從嚴格意義上說(shuō)不能算作是錯誤,但卻很可能成為錯誤的棲身之所。一個(gè)優(yōu)秀的Linux程序員應該盡量避免產(chǎn)生警告信息,使自己的代碼始終保持簡(jiǎn)潔、優(yōu)美和健壯的特性。
在處理警告方面,另一個(gè)常用的編譯選項是-Werror,它要求GCC將所有的警告當成錯誤進(jìn)行處理,這在使用自動(dòng)編譯工具(如Make等)時(shí)非常有用。如果編譯時(shí)帶上-Werror選項,那么GCC會(huì )在所有產(chǎn)生警告的地方停止編譯,迫使程序員對自己的代碼進(jìn)行修改。只有當相應的警告信息消除時(shí),才可能將編譯過(guò)程繼續朝前推進(jìn)。執行情況如下:
|
對Linux程序員來(lái)講,GCC給出的警告信息是很有價(jià)值的,它們不僅可以幫助程序員寫(xiě)出更加健壯的程序,而且還是跟蹤和調試程序的有力工具。建議在用GCC編譯源代碼時(shí)始終帶上-Wall選項,并把它逐漸培養成為一種習慣,這對找出常見(jiàn)的隱式編程錯誤很有幫助。
庫依賴(lài)
在Linux 下開(kāi)發(fā)軟件時(shí),完全不使用第三方函數庫的情況是比較少見(jiàn)的,通常來(lái)講都需要借助一個(gè)或多個(gè)函數庫的支持才能夠完成相應的功能。從程序員的角度看,函數庫實(shí)際上就是一些頭文件(.h)和庫文件(.so或者.a)的集合。雖然Linux下的大多數函數都默認將頭文件放到/usr/include/目錄下,而庫文件則放到/usr/lib/目錄下,但并不是所有的情況都是這樣。正因如此,GCC在編譯時(shí)必須有自己的辦法來(lái)查找所需要的頭文件和庫文件。
GCC采用搜索目錄的辦法來(lái)查找所需要的文件,-I選項可以向GCC的頭文件搜索路徑中添加新的目錄。例如,如果在/home/xiaowp/include/目錄下有編譯時(shí)所需要的頭文件,為了讓GCC能夠順利地找到它們,就可以使用-I選項:
|
同樣,如果使用了不在標準位置的庫文件,那么可以通過(guò)-L選項向GCC的庫文件搜索路徑中添加新的目錄。例如,如果在/home/xiaowp/lib/目錄下有鏈接時(shí)所需要的庫文件libfoo.so,為了讓GCC能夠順利地找到它,可以使用下面的命令:
|
值得好好解釋一下的是-l選項,它指示GCC去連接庫文件libfoo.so。Linux下的庫文件在命名時(shí)有一個(gè)約定,那就是應該以lib三個(gè)字母開(kāi)頭,由于所有的庫文件都遵循了同樣的規范,因此在用-l選項指定鏈接的庫文件名時(shí)可以省去lib三個(gè)字母,也就是說(shuō)GCC在對-lfoo進(jìn)行處理時(shí),會(huì )自動(dòng)去鏈接名為libfoo.so的文件。
Linux下的庫文件分為兩大類(lèi)分別是動(dòng)態(tài)鏈接庫(通常以.so結尾)和靜態(tài)鏈接庫(通常以.a結尾),兩者的差別僅在程序執行時(shí)所需的代碼是在運行時(shí)動(dòng)態(tài)加載的,還是在編譯時(shí)靜態(tài)加載的。默認情況下,GCC在鏈接時(shí)優(yōu)先使用動(dòng)態(tài)鏈接庫,只有當動(dòng)態(tài)鏈接庫不存在時(shí)才考慮使用靜態(tài)鏈接庫,如果需要的話(huà)可以在編譯時(shí)加上-static選項,強制使用靜態(tài)鏈接庫。例如,如果在/home/xiaowp/lib/目錄下有鏈接時(shí)所需要的庫文件libfoo.so和libfoo.a,為了讓 GCC在鏈接時(shí)只用到靜態(tài)鏈接庫,可以使用下面的命令:
|
c語(yǔ)言相關(guān)文章:c語(yǔ)言教程
linux相關(guān)文章:linux教程
c++相關(guān)文章:c++教程
評論