新聞中心

        EEPW首頁 > 嵌入式系統(tǒng) > Linux操作系統(tǒng)中GCC的應(yīng)用介紹(上)

        Linux操作系統(tǒng)中GCC的應(yīng)用介紹(上)

        ——
        作者: 時間:2007-04-18 來源: 收藏

        在為Linux開發(fā)應(yīng)用程序時,絕大多數(shù)情況下使用的都是C語言,因此幾乎每一位Linux程序員面臨的首要問題都是如何靈活運用C。目前Linux 下最常用的C語言是GCC(GNU Compiler Collection),它是GNU項目中符合ANSI C標準的編譯系統(tǒng),能夠編譯用C、C++和Object C等語言編寫的程序。GCC不僅功能非常強大,結(jié)構(gòu)也異常靈活。最值得稱道的一點就是它可以通過不同的前端模塊來支持各種語言,如Java、 Fortran、Pascal、Modula-3和Ada等。

        開放、自由和靈活是Linux的魅力所在,而這一點在GCC上的體現(xiàn)就是程序員通過它能夠更好地控制整個編譯過程。在使用GCC編譯程序時,編譯過程可以被細分為四個階段:

        ◆ 預(yù)處理(Pre-Processing)

        ◆ 編譯(Compiling)

        ◆ 匯編(Assembling)

        ◆ 鏈接(Linking)

        Linux程序員可以根據(jù)自己的需要讓 GCC在編譯的任何階段結(jié)束,以便檢查或使用在該階段的輸出信息,或者對最后生成的二進制文件進行控制,以便通過加入不同數(shù)量和種類的調(diào)試代碼來為今后的調(diào)試做好準備。和其它常用的編譯器一樣,GCC也提供了靈活而強大的代碼優(yōu)化功能,利用它可以生成執(zhí)行效率更高的代碼。

        GCC提供了30多條警告信息和三個警告級別,使用它們有助于增強程序的穩(wěn)定性和可移植性。此外,GCC還對標準的C和C++語言進行了大量的擴展,提高程序的執(zhí)行效率,有助于編譯器進行代碼優(yōu)化,能夠減輕編程的工作量。

        GCC起步

        在學(xué)習(xí)使用GCC之前,下面的這個例子能夠幫助用戶迅速理解GCC的工作原理,并將其立即運用到實際的項目開發(fā)中去。首先用熟悉的輸入清單1所示的代碼:

        清單1:hello.c

        #include 
        int main(void)
        {
        printf ("Hello world, Linux programming!\n");
        return 0;
        }

        然后執(zhí)行下面的命令編譯和運行這段程序:

        # gcc hello.c -o hello
        # ./hello
        Hello world, Linux programming!

        從程序員的角度看,只需簡單地執(zhí)行一條GCC命令就可以了,但從編譯器的角度來看,卻需要完成一系列非常繁雜的工作。首先,GCC需要調(diào)用預(yù)處理程序 cpp,由它負責展開在源文件中定義的宏,并向其中插入“#include”語句所包含的內(nèi)容;接著,GCC會調(diào)用ccl和as將處理后的源代碼編譯成目標代碼;最后,GCC會調(diào)用鏈接程序ld,把生成的目標代碼鏈接成一個可執(zhí)行程序。

        為了更好地理解GCC的工作過程,可以把上述編譯過程分成幾個步驟單獨進行,并觀察每步的運行結(jié)果。第一步是進行預(yù)編譯,使用-E參數(shù)可以讓GCC在預(yù)處理結(jié)束后停止編譯過程:

        # gcc -E hello.c -o hello.i

        此時若查看hello.cpp文件中的內(nèi)容,會發(fā)現(xiàn)stdio.h的內(nèi)容確實都插到文件里去了,而其它應(yīng)當被預(yù)處理的宏定義也都做了相應(yīng)的處理。下一步是將hello.i編譯為目標代碼,這可以通過使用-c參數(shù)來完成:

        # gcc -c hello.i -o hello.o

        GCC默認將.i文件看成是預(yù)處理后的C語言源代碼,因此上述命令將自動跳過預(yù)處理步驟而開始執(zhí)行編譯過程,也可以使用-x參數(shù)讓GCC從指定的步驟開始編譯。最后一步是將生成的目標文件鏈接成可執(zhí)行文件:

        # gcc hello.o -o hello

        在為Linux開發(fā)應(yīng)用程序時,絕大多數(shù)情況下使用的都是C語言,因此幾乎每一位Linux程序員面臨的首要問題都是如何靈活運用C編譯器。目前Linux 下最常用的C語言編譯器是GCC(GNU Compiler Collection),它是GNU項目中符合ANSI C標準的編譯系統(tǒng),能夠編譯用C、C++和Object C等語言編寫的程序。GCC不僅功能非常強大,結(jié)構(gòu)也異常靈活。最值得稱道的一點就是它可以通過不同的前端模塊來支持各種語言,如Java、 Fortran、Pascal、Modula-3和Ada等。

        {{分頁}}

        開放、自由和靈活是Linux的魅力所在,而這一點在GCC上的體現(xiàn)就是程序員通過它能夠更好地控制整個編譯過程。在使用GCC編譯程序時,編譯過程可以被細分為四個階段:

        ◆ 預(yù)處理(Pre-Processing)

        ◆ 編譯(Compiling)

        ◆ 匯編(Assembling)

        ◆ 鏈接(Linking)

        Linux程序員可以根據(jù)自己的需要讓 GCC在編譯的任何階段結(jié)束,以便檢查或使用編譯器在該階段的輸出信息,或者對最后生成的二進制文件進行控制,以便通過加入不同數(shù)量和種類的調(diào)試代碼來為今后的調(diào)試做好準備。和其它常用的編譯器一樣,GCC也提供了靈活而強大的代碼優(yōu)化功能,利用它可以生成執(zhí)行效率更高的代碼。

        GCC提供了30多條警告信息和三個警告級別,使用它們有助于增強程序的穩(wěn)定性和可移植性。此外,GCC還對標準的C和C++語言進行了大量的擴展,提高程序的執(zhí)行效率,有助于編譯器進行代碼優(yōu)化,能夠減輕編程的工作量。

        GCC起步

        在學(xué)習(xí)使用GCC之前,下面的這個例子能夠幫助用戶迅速理解GCC的工作原理,并將其立即運用到實際的項目開發(fā)中去。首先用熟悉的輸入清單1所示的代碼:

        清單1:hello.c

        #include 
        int main(void)
        {
        printf ("Hello world, Linux programming!\n");
        return 0;
        }

        然后執(zhí)行下面的命令編譯和運行這段程序:

        # gcc hello.c -o hello
        # ./hello
        Hello world, Linux programming!

        從程序員的角度看,只需簡單地執(zhí)行一條GCC命令就可以了,但從編譯器的角度來看,卻需要完成一系列非常繁雜的工作。首先,GCC需要調(diào)用預(yù)處理程序 cpp,由它負責展開在源文件中定義的宏,并向其中插入“#include”語句所包含的內(nèi)容;接著,GCC會調(diào)用ccl和as將處理后的源代碼編譯成目標代碼;最后,GCC會調(diào)用鏈接程序ld,把生成的目標代碼鏈接成一個可執(zhí)行程序。

        為了更好地理解GCC的工作過程,可以把上述編譯過程分成幾個步驟單獨進行,并觀察每步的運行結(jié)果。第一步是進行預(yù)編譯,使用-E參數(shù)可以讓GCC在預(yù)處理結(jié)束后停止編譯過程:

        # gcc -E hello.c -o hello.i

        此時若查看hello.cpp文件中的內(nèi)容,會發(fā)現(xiàn)stdio.h的內(nèi)容確實都插到文件里去了,而其它應(yīng)當被預(yù)處理的宏定義也都做了相應(yīng)的處理。下一步是將hello.i編譯為目標代碼,這可以通過使用-c參數(shù)來完成:

        # gcc -c hello.i -o hello.o

        GCC默認將.i文件看成是預(yù)處理后的C語言源代碼,因此上述命令將自動跳過預(yù)處理步驟而開始執(zhí)行編譯過程,也可以使用-x參數(shù)讓GCC從指定的步驟開始編譯。最后一步是將生成的目標文件鏈接成可執(zhí)行文件:

        # gcc hello.o -o hello

        在采用模塊化的設(shè)計思想進行軟件開發(fā)時,通常整個程序是由多個源文件組成的,相應(yīng)地也就形成了多個編譯單元,使用GCC能夠很好地管理這些編譯單元。假設(shè)有一個由foo1.c和foo2.c兩個源文件組成的程序,為了對它們進行編譯,并最終生成可執(zhí)行程序foo,可以使用下面這條命令:

        # gcc foo1.c foo2.c -o foo

        如果同時處理的文件不止一個,GCC仍然會按照預(yù)處理、編譯和鏈接的過程依次進行。如果深究起來,上面這條命令大致相當于依次執(zhí)行如下三條命令:

        # gcc -c foo1.c -o foo1.o
        # gcc -c foo2.c -o foo2.o
        # gcc foo1.o foo2.o -o foo

        在編譯一個包含許多源文件的工程時,若只用一條GCC命令來完成編譯是非常浪費時間的。假設(shè)項目中有100個源文件需要編譯,并且每個源文件中都包含 10000行代碼,如果像上面那樣僅用一條GCC命令來完成編譯工作,那么GCC需要將每個源文件都重新編譯一遍,然后再全部連接起來。很顯然,這樣浪費的時間相當多,尤其是當用戶只是修改了其中某一個文件的時候,完全沒有必要將每個文件都重新編譯一遍,因為很多已經(jīng)生成的目標文件是不會改變的。要解決這個問題,關(guān)鍵是要靈活運用GCC,同時還要借助像Make這樣的工具。

        警告提示功能

        GCC包含完整的出錯檢查和警告提示功能,它們可以幫助Linux程序員寫出更加專業(yè)和優(yōu)美的代碼。先來讀讀清單2所示的程序,這段代碼寫得很糟糕,仔細檢查一下不難挑出很多毛病:

        {{分頁}}

        ◆main函數(shù)的返回值被聲明為void,但實際上應(yīng)該是int;

        ◆使用了GNU語法擴展,即使用long long來聲明64位整數(shù),不符合ANSI/ISO C語言標準;

        ◆main函數(shù)在終止前沒有調(diào)用return語句。

        清單2:illcode.c

        #include 
        void main(void)
        {
        long long int var = 1;
        printf("It is not standard C code!\n");
        }

        下面來看看GCC是如何幫助程序員來發(fā)現(xiàn)這些錯誤的。當GCC在編譯不符合ANSI/ISO C語言標準的源代碼時,如果加上了-pedantic選項,那么使用了擴展語法的地方將產(chǎn)生相應(yīng)的警告信息:

        # gcc -pedantic illcode.c -o illcode
        illcode.c: In function `main':
        illcode.c:9: ISO C89 does not support `long long'
        illcode.c:8: return type of `main' is not `int'

        需要注意的是,-pedantic編譯選項并不能保證被編譯程序與ANSI/ISO C標準的完全兼容,它僅僅只能用來幫助Linux程序員離這個目標越來越近。或者換句話說,-pedantic選項能夠幫助程序員發(fā)現(xiàn)一些不符合ANSI/ISO C標準的代碼,但不是全部,事實上只有ANSI/ISO C語言標準中要求進行編譯器診斷的那些情況,才有可能被GCC發(fā)現(xiàn)并提出警告。

        除了-pedantic之外,GCC還有一些其它編譯選項也能夠產(chǎn)生有用的警告信息。這些選項大多以-W開頭,其中最有價值的當數(shù)-Wall了,使用它能夠使GCC產(chǎn)生盡可能多的警告信息:

        # gcc -Wall illcode.c -o illcode
        illcode.c:8: warning: return type of `main' is not `int'
        illcode.c: In function `main':
        illcode.c:9: warning: unused variable `var'
         

        GCC給出的警告信息雖然從嚴格意義上說不能算作是錯誤,但卻很可能成為錯誤的棲身之所。一個優(yōu)秀的Linux程序員應(yīng)該盡量避免產(chǎn)生警告信息,使自己的代碼始終保持簡潔、優(yōu)美和健壯的特性。

        在處理警告方面,另一個常用的編譯選項是-Werror,它要求GCC將所有的警告當成錯誤進行處理,這在使用自動編譯工具(如Make等)時非常有用。如果編譯時帶上-Werror選項,那么GCC會在所有產(chǎn)生警告的地方停止編譯,迫使程序員對自己的代碼進行修改。只有當相應(yīng)的警告信息消除時,才可能將編譯過程繼續(xù)朝前推進。執(zhí)行情況如下:

        # gcc -Wall -Werror illcode.c -o illcode
        cc1: warnings being treated as errors
        illcode.c:8: warning: return type of `main' is not `int'
        illcode.c: In function `main':
        illcode.c:9: warning: unused variable `var'

        對Linux程序員來講,GCC給出的警告信息是很有價值的,它們不僅可以幫助程序員寫出更加健壯的程序,而且還是跟蹤和調(diào)試程序的有力工具。建議在用GCC編譯源代碼時始終帶上-Wall選項,并把它逐漸培養(yǎng)成為一種習(xí)慣,這對找出常見的隱式編程錯誤很有幫助。

        庫依賴

        在Linux 下開發(fā)軟件時,完全不使用第三方函數(shù)庫的情況是比較少見的,通常來講都需要借助一個或多個函數(shù)庫的支持才能夠完成相應(yīng)的功能。從程序員的角度看,函數(shù)庫實際上就是一些頭文件(.h)和庫文件(.so或者.a)的集合。雖然Linux下的大多數(shù)函數(shù)都默認將頭文件放到/usr/include/目錄下,而庫文件則放到/usr/lib/目錄下,但并不是所有的情況都是這樣。正因如此,GCC在編譯時必須有自己的辦法來查找所需要的頭文件和庫文件。

        GCC采用搜索目錄的辦法來查找所需要的文件,-I選項可以向GCC的頭文件搜索路徑中添加新的目錄。例如,如果在/home/xiaowp/include/目錄下有編譯時所需要的頭文件,為了讓GCC能夠順利地找到它們,就可以使用-I選項:

        # gcc foo.c -I /home/xiaowp/include -o foo

        同樣,如果使用了不在標準位置的庫文件,那么可以通過-L選項向GCC的庫文件搜索路徑中添加新的目錄。例如,如果在/home/xiaowp/lib/目錄下有鏈接時所需要的庫文件libfoo.so,為了讓GCC能夠順利地找到它,可以使用下面的命令:

        # gcc foo.c -L /home/xiaowp/lib -lfoo -o foo

        值得好好解釋一下的是-l選項,它指示GCC去連接庫文件libfoo.so。Linux下的庫文件在命名時有一個約定,那就是應(yīng)該以lib三個字母開頭,由于所有的庫文件都遵循了同樣的規(guī)范,因此在用-l選項指定鏈接的庫文件名時可以省去lib三個字母,也就是說GCC在對-lfoo進行處理時,會自動去鏈接名為libfoo.so的文件。

        Linux下的庫文件分為兩大類分別是動態(tài)鏈接庫(通常以.so結(jié)尾)和靜態(tài)鏈接庫(通常以.a結(jié)尾),兩者的差別僅在程序執(zhí)行時所需的代碼是在運行時動態(tài)加載的,還是在編譯時靜態(tài)加載的。默認情況下,GCC在鏈接時優(yōu)先使用動態(tài)鏈接庫,只有當動態(tài)鏈接庫不存在時才考慮使用靜態(tài)鏈接庫,如果需要的話可以在編譯時加上-static選項,強制使用靜態(tài)鏈接庫。例如,如果在/home/xiaowp/lib/目錄下有鏈接時所需要的庫文件libfoo.so和libfoo.a,為了讓 GCC在鏈接時只用到靜態(tài)鏈接庫,可以使用下面的命令:

        # gcc foo.c -L /home/xiaowp/lib -static -lfoo -o foo

        c語言相關(guān)文章:c語言教程


        linux相關(guān)文章:linux教程


        c++相關(guān)文章:c++教程




        評論


        相關(guān)推薦

        技術(shù)專區(qū)

        關(guān)閉
        主站蜘蛛池模板: 洛浦县| 齐河县| 偃师市| 连云港市| 东宁县| 墨竹工卡县| 罗定市| 佳木斯市| 临洮县| 松江区| 南陵县| 依兰县| 连平县| 綦江县| 渝北区| 达州市| 靖远县| 玉溪市| 安龙县| 桑日县| 连州市| 沁水县| 太白县| 寿宁县| 介休市| 浦北县| 成安县| 井冈山市| 阿勒泰市| 彭州市| 手游| 大邑县| 太保市| 青浦区| 利津县| 崇义县| 休宁县| 肥西县| 农安县| 榆树市| 南和县|