GCC介紹
在為L(zhǎng)inux開發(fā)應(yīng)用程序時(shí),絕大多數(shù)情況下使用的都是C語(yǔ)言,因此幾乎每一位Linux程序員面臨的首要問(wèn)題都是如何
靈活運(yùn)用C編譯器.目前Linux下最常用的C語(yǔ)言編譯器是GCC(GNU Compiler Collection),它是GNU項(xiàng)目中符合ANSI C
標(biāo)準(zhǔn)的編譯系統(tǒng),能夠編譯用C、C++和Object C等語(yǔ)言編寫的程序.GCC不僅功能非常強(qiáng)大,結(jié)構(gòu)也異常靈活.最值得稱
道的一點(diǎn)就是它可以通過(guò)不同的前端模塊來(lái)支持各種語(yǔ)言,如Java、Fortran、Pascal、Modula-3和Ada等.
開放、自由和靈活是Linux的魅力所在,而這一點(diǎn)在GCC上的體現(xiàn)就是程序員通過(guò)它能夠更好地控制整個(gè)編譯過(guò)程.在
使用GCC編譯程序時(shí),編譯過(guò)程可以被細(xì)分為四個(gè)階段:
◆ 預(yù)處理(Pre-Processing)
◆ 編譯(Compiling)
◆ 匯編(Assembling)
◆ 鏈接(Linking)
Linux程序員可以根據(jù)自己的需要讓GCC在編譯的任何階段結(jié)束,以便檢查或使用編譯器在該階段的輸出信息,或者對(duì)
最后生成的二進(jìn)制文件進(jìn)行控制,以便通過(guò)加入不同數(shù)量和種類的調(diào)試代碼來(lái)為今后的調(diào)試做好準(zhǔn)備.和其它常用的
編譯器一樣,GCC也提供了靈活而強(qiáng)大的代碼優(yōu)化功能,利用它可以生成執(zhí)行效率更高的代碼.
GCC提供了30多條警告信息和三個(gè)警告級(jí)別,使用它們有助于增強(qiáng)程序的穩(wěn)定性和可移植性.此外,GCC還對(duì)標(biāo)準(zhǔn)的C和
C++語(yǔ)言進(jìn)行了大量的擴(kuò)展,提高程序的執(zhí)行效率,有助于編譯器進(jìn)行代碼優(yōu)化,能夠減輕編程的工作量.
GCC起步
在學(xué)習(xí)使用GCC之前,下面的這個(gè)例子能夠幫助用戶迅速理解GCC的工作原理,并將其立即運(yùn)用到實(shí)際的項(xiàng)目開發(fā)中去.
首先用熟悉的編輯器輸入清單1所示的代碼:
清單1:hello.c
#include <stdio.h>
int main(void)
{
printf (Hello world, Linux programming!\\n);
return 0;
}
然后執(zhí)行下面的命令編譯和運(yùn)行這段程序:
# gcc hello.c -o hello
# ./hello
Hello world, Linux programming!
從程序員的角度看,只需簡(jiǎn)單地執(zhí)行一條GCC命令就可以了,但從編譯器的角度來(lái)看,卻需要完成一系列非常繁雜的工
作.首先,GCC需要調(diào)用預(yù)處理程序cpp,由它負(fù)責(zé)展開在源文件中定義的宏,并向其中插入“#include”語(yǔ)句所包含的
內(nèi)容;接著,GCC會(huì)調(diào)用ccl和as將處理后的源代碼編譯成目標(biāo)代碼;最后,GCC會(huì)調(diào)用鏈接程序ld,把生成的目標(biāo)代碼
鏈接成一個(gè)可執(zhí)行程序.
為了更好地理解GCC的工作過(guò)程,可以把上述編譯過(guò)程分成幾個(gè)步驟單獨(dú)進(jìn)行,并觀察每步的運(yùn)行結(jié)果.第一步是進(jìn)行
預(yù)編譯,使用-E參數(shù)可以讓GCC在預(yù)處理結(jié)束后停止編譯過(guò)程:
# gcc -E hello.c -o hello.i
此時(shí)若查看hello.cpp文件中的內(nèi)容,會(huì)發(fā)現(xiàn)stdio.h的內(nèi)容確實(shí)都插到文件里去了,而其它應(yīng)當(dāng)被預(yù)處理的宏定義也
都做了相應(yīng)的處理.下一步是將hello.i編譯為目標(biāo)代碼,這可以通過(guò)使用-c參數(shù)來(lái)完成:
# gcc -c hello.i -o hello.o
GCC默認(rèn)將.i文件看成是預(yù)處理后的C語(yǔ)言源代碼,因此上述命令將自動(dòng)跳過(guò)預(yù)處理步驟而開始執(zhí)行編譯過(guò)程,也可以
使用-x參數(shù)讓GCC從指定的步驟開始編譯.最后一步是將生成的目標(biāo)文件鏈接成可執(zhí)行文件:
# gcc hello.o -o hello
在采用模塊化的設(shè)計(jì)思想進(jìn)行軟件開發(fā)時(shí),通常整個(gè)程序是由多個(gè)源文件組成的,相應(yīng)地也就形成了多個(gè)編譯單元,使
用GCC能夠很好地管理這些編譯單元.假設(shè)有一個(gè)由foo1.c和foo2.c兩個(gè)源文件組成的程序,為了對(duì)它們進(jìn)行編譯,并
最終生成可執(zhí)行程序foo,可以使用下面這條命令:
# gcc foo1.c foo2.c -o foo
如果同時(shí)處理的文件不止一個(gè),GCC仍然會(huì)按照預(yù)處理、編譯和鏈接的過(guò)程依次進(jìn)行.如果深究起來(lái),上面這條命令大
致相當(dāng)于依次執(zhí)行如下三條命令:
# gcc -c foo1.c -o foo1.o
# gcc -c foo2.c -o foo2.o
# gcc foo1.o foo2.o -o foo
在編譯一個(gè)包含許多源文件的工程時(shí),若只用一條GCC命令來(lái)完成編譯是非常浪費(fèi)時(shí)間的.假設(shè)項(xiàng)目中有100個(gè)源文件
需要編譯,并且每個(gè)源文件中都包含10000行代碼,如果像上面那樣僅用一條GCC命令來(lái)完成編譯工作,那么GCC需要將
每個(gè)源文件都重新編譯一遍,然后再全部連接起來(lái).很顯然,這樣浪費(fèi)的時(shí)間相當(dāng)多,尤其是當(dāng)用戶只是修改了其中某
一個(gè)文件的時(shí)候,完全沒有必要將每個(gè)文件都重新編譯一遍,因?yàn)楹芏嘁呀?jīng)生成的目標(biāo)文件是不會(huì)改變的.要解決這個(gè)
問(wèn)題,關(guān)鍵是要靈活運(yùn)用GCC,同時(shí)還要借助像Make這樣的工具.
警告提示功能
GCC包含完整的出錯(cuò)檢查和警告提示功能,它們可以幫助Linux程序員寫出更加專業(yè)和優(yōu)美的代碼.先來(lái)讀讀清單2所示
的程序,這段代碼寫得很糟糕,仔細(xì)檢查一下不難挑出很多毛病:
◆main函數(shù)的返回值被聲明為void,但實(shí)際上應(yīng)該是int;
◆使用了GNU語(yǔ)法擴(kuò)展,即使用long long來(lái)聲明64位整數(shù),不符合ANSI/ISO C語(yǔ)言標(biāo)準(zhǔn);
◆main函數(shù)在終止前沒有調(diào)用return語(yǔ)句.
清單2:illcode.c
#include <stdio.h>
void main(void)
{
long long int var = 1;
printf(It is not standard C code!\\n);
}
下面來(lái)看看GCC是如何幫助程序員來(lái)發(fā)現(xiàn)這些錯(cuò)誤的.當(dāng)GCC在編譯不符合ANSI/ISO C語(yǔ)言標(biāo)準(zhǔn)的源代碼時(shí),如果加上
了-pedantic選項(xiàng),那么使用了擴(kuò)展語(yǔ)法的地方將產(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編譯選項(xiàng)并不能保證被編譯程序與ANSI/ISO C標(biāo)準(zhǔn)的完全兼容,它僅僅只能用來(lái)幫助Linux
程序員離這個(gè)目標(biāo)越來(lái)越近.或者換句話說(shuō),-pedantic選項(xiàng)能夠幫助程序員發(fā)現(xiàn)一些不符合ANSI/ISO C標(biāo)準(zhǔn)的代碼,
但不是全部,事實(shí)上只有ANSI/ISO C語(yǔ)言標(biāo)準(zhǔn)中要求進(jìn)行編譯器診斷的那些情況,才有可能被GCC發(fā)現(xiàn)并提出警告.
除了-pedantic之外,GCC還有一些其它編譯選項(xiàng)也能夠產(chǎn)生有用的警告信息.這些選項(xiàng)大多以-W開頭,其中最有價(jià)值的
當(dāng)數(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án)格意義上說(shuō)不能算作是錯(cuò)誤,但卻很可能成為錯(cuò)誤的棲身之所.一個(gè)優(yōu)秀的Linux程序員
應(yīng)該盡量避免產(chǎn)生警告信息,使自己的代碼始終保持簡(jiǎn)潔、優(yōu)美和健壯的特性.
在處理警告方面,另一個(gè)常用的編譯選項(xiàng)是-Werror,它要求GCC將所有的警告當(dāng)成錯(cuò)誤進(jìn)行處理,這在使用自動(dòng)編譯工
具(如Make等)時(shí)非常有用.如果編譯時(shí)帶上-Werror選項(xiàng),那么GCC會(huì)在所有產(chǎn)生警告的地方停止編譯,迫使程序員對(duì)自
己的代碼進(jìn)行修改.只有當(dāng)相應(yīng)的警告信息消除時(shí),才可能將編譯過(guò)程繼續(xù)朝前推進(jìn).執(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'
對(duì)Linux程序員來(lái)講,GCC給出的警告信息是很有價(jià)值的,它們不僅可以幫助程序員寫出更加健壯的程序,而且還是跟蹤
和調(diào)試程序的有力工具.建議在用GCC編譯源代碼時(shí)始終帶上-Wall選項(xiàng),并把它逐漸培養(yǎng)成為一種習(xí)慣,這對(duì)找出常見
的隱式編程錯(cuò)誤很有幫助.
庫(kù)依賴
在Linux下開發(fā)軟件時(shí),完全不使用第三方函數(shù)庫(kù)的情況是比較少見的,通常來(lái)講都需要借助一個(gè)或多個(gè)函數(shù)庫(kù)的支持
才能夠完成相應(yīng)的功能.從程序員的角度看,函數(shù)庫(kù)實(shí)際上就是一些頭文件(.h)和庫(kù)文件(.so或者.a)的集合.雖然
Linux下的大多數(shù)函數(shù)都默認(rèn)將頭文件放到/usr/include/目錄下,而庫(kù)文件則放到/usr/lib/目錄下,但并不是所有的
情況都是這樣.正因如此,GCC在編譯時(shí)必須有自己的辦法來(lái)查找所需要的頭文件和庫(kù)文件.
GCC采用搜索目錄的辦法來(lái)查找所需要的文件,-I選項(xiàng)可以向GCC的頭文件搜索路徑中添加新的目錄.例如,如果
在/home/xiaowp/include/目錄下有編譯時(shí)所需要的頭文件,為了讓GCC能夠順利地找到它們,就可以使用-I選項(xiàng):
# gcc foo.c -I /home/xiaowp/include -o foo
同樣,如果使用了不在標(biāo)準(zhǔn)位置的庫(kù)文件,那么可以通過(guò)-L選項(xiàng)向GCC的庫(kù)文件搜索路徑中添加新的目錄.例如,如果在
/home/xiaowp/lib/目錄下有鏈接時(shí)所需要的庫(kù)文件libfoo.so,為了讓GCC能夠順利地找到它,可以使用下面的命令:
# gcc foo.c -L /home/xiaowp/lib -lfoo -o foo
值得好好解釋一下的是-l選項(xiàng),它指示GCC去連接庫(kù)文件libfoo.so.Linux下的庫(kù)文件在命名時(shí)有一個(gè)約定,那就是應(yīng)
該以lib三個(gè)字母開頭,由于所有的庫(kù)文件都遵循了同樣的規(guī)范,因此在用-l選項(xiàng)指定鏈接的庫(kù)文件名時(shí)可以省去lib
三個(gè)字母,也就是說(shuō)GCC在對(duì)-lfoo進(jìn)行處理時(shí),會(huì)自動(dòng)去鏈接名為libfoo.so的文件.
Linux下的庫(kù)文件分為兩大類分別是動(dòng)態(tài)鏈接庫(kù)(通常以.so結(jié)尾)和靜態(tài)鏈接庫(kù)(通常以.a結(jié)尾),兩者的差別僅在程序
執(zhí)行時(shí)所需的代碼是在運(yùn)行時(shí)動(dòng)態(tài)加載的,還是在編譯時(shí)靜態(tài)加載的.默認(rèn)情況下,GCC在鏈接時(shí)優(yōu)先使用動(dòng)態(tài)鏈接庫(kù),
只有當(dāng)動(dòng)態(tài)鏈接庫(kù)不存在時(shí)才考慮使用靜態(tài)鏈接庫(kù),如果需要的話可以在編譯時(shí)加上-static選項(xiàng),強(qiáng)制使用靜態(tài)鏈接
庫(kù).例如,如果在/home/xiaowp/lib/目錄下有鏈接時(shí)所需要的庫(kù)文件libfoo.so和libfoo.a,為了讓GCC在鏈接時(shí)只用
到靜態(tài)鏈接庫(kù),可以使用下面的命令:
# gcc foo.c -L /home/xiaowp/lib -static -lfoo -o foo
代碼優(yōu)化
代碼優(yōu)化指的是編譯器通過(guò)分析源代碼,找出其中尚未達(dá)到最優(yōu)的部分,然后對(duì)其重新進(jìn)行組合,目的是改善程序的執(zhí)
行性能.GCC提供的代碼優(yōu)化功能非常強(qiáng)大,它通過(guò)編譯選項(xiàng)-On來(lái)控制優(yōu)化代碼的生成,其中n是一個(gè)代表優(yōu)化級(jí)別的
整數(shù).對(duì)于不同版本的GCC來(lái)講,n的取值范圍及其對(duì)應(yīng)的優(yōu)化效果可能并不完全相同,比較典型的范圍是從0變化到2或
3.
編譯時(shí)使用選項(xiàng)-O可以告訴GCC同時(shí)減小代碼的長(zhǎng)度和執(zhí)行時(shí)間,其效果等價(jià)于-O1.在這一級(jí)別上能夠進(jìn)行的優(yōu)化類
型雖然取決于目標(biāo)處理器,但一般都會(huì)包括線程跳轉(zhuǎn)(Thread Jump)和延遲退棧(Deferred Stack Pops)兩種優(yōu)化.選
項(xiàng)-O2告訴GCC除了完成所有-O1級(jí)別的優(yōu)化之外,同時(shí)還要進(jìn)行一些額外的調(diào)整工作,如處理器指令調(diào)度等.選項(xiàng)-O3則
除了完成所有-O2級(jí)別的優(yōu)化之外,還包括循環(huán)展開和其它一些與處理器特性相關(guān)的優(yōu)化工作.通常來(lái)說(shuō),數(shù)字越大優(yōu)
化的等級(jí)越高,同時(shí)也就意味著程序的運(yùn)行速度越快.許多Linux程序員都喜歡使用-O2選項(xiàng),因?yàn)樗趦?yōu)化長(zhǎng)度、編譯
時(shí)間和代碼大小之間,取得了一個(gè)比較理想的平衡點(diǎn).
下面通過(guò)具體實(shí)例來(lái)感受一下GCC的代碼優(yōu)化功能,所用程序如清單3所示.
清單3:optimize.c
#include <stdio.h>
int main(void)
{
double counter;
double result;
double temp;
for (counter = 0;
counter < 2000.0 * 2000.0 * 2000.0 / 20.0 + 2020;
counter += (5 - 1) / 4) {
temp = counter / 1979;
result = counter;
}
printf(Result is %lf\\n, result);
return 0;
}
首先不加任何優(yōu)化選項(xiàng)進(jìn)行編譯:
# gcc -Wall optimize.c -o optimize
借助Linux提供的time命令,可以大致統(tǒng)計(jì)出該程序在運(yùn)行時(shí)所需要的時(shí)間:
# time ./optimize
Result is 400002019.000000
real 0m14.942s
user 0m14.940s
sys 0m0.000s
接下去使用優(yōu)化選項(xiàng)來(lái)對(duì)代碼進(jìn)行優(yōu)化處理:
# gcc -Wall -O optimize.c -o optimize
在同樣的條件下再次測(cè)試一下運(yùn)行時(shí)間:
# time ./optimize
Result is 400002019.000000
real 0m3.256s
user 0m3.240s
sys 0m0.000s
對(duì)比兩次執(zhí)行的輸出結(jié)果不難看出,程序的性能的確得到了很大幅度的改善,由原來(lái)的14秒縮短到了3秒.這個(gè)例子是
專門針對(duì)GCC的優(yōu)化功能而設(shè)計(jì)的,因此優(yōu)化前后程序的執(zhí)行速度發(fā)生了很大的改變.盡管GCC的代碼優(yōu)化功能非常強(qiáng)
大,但作為一名優(yōu)秀的Linux程序員,首先還是要力求能夠手工編寫出高質(zhì)量的代碼.如果編寫的代碼簡(jiǎn)短,并且邏輯性
強(qiáng),編譯器就不會(huì)做更多的工作,甚至根本用不著優(yōu)化.
優(yōu)化雖然能夠給程序帶來(lái)更好的執(zhí)行性能,但在如下一些場(chǎng)合中應(yīng)該避免優(yōu)化代碼:
◆ 程序開發(fā)的時(shí)候 優(yōu)化等級(jí)越高,消耗在編譯上的時(shí)間就越長(zhǎng),因此在開發(fā)的時(shí)候最好不要使用優(yōu)化選項(xiàng),只有到軟
件發(fā)行或開發(fā)結(jié)束的時(shí)候,才考慮對(duì)最終生成的代碼進(jìn)行優(yōu)化.
◆ 資源受限的時(shí)候 一些優(yōu)化選項(xiàng)會(huì)增加可執(zhí)行代碼的體積,如果程序在運(yùn)行時(shí)能夠申請(qǐng)到的內(nèi)存資源非常緊張(如
一些實(shí)時(shí)嵌入式設(shè)備),那就不要對(duì)代碼進(jìn)行優(yōu)化,因?yàn)橛蛇@帶來(lái)的負(fù)面影響可能會(huì)產(chǎn)生非常嚴(yán)重的后果.
◆ 跟蹤調(diào)試的時(shí)候 在對(duì)代碼進(jìn)行優(yōu)化的時(shí)候,某些代碼可能會(huì)被刪除或改寫,或者為了取得更佳的性能而進(jìn)行重組,
從而使跟蹤和調(diào)試變得異常困難.
調(diào)試
一個(gè)功能強(qiáng)大的調(diào)試器不僅為程序員提供了跟蹤程序執(zhí)行的手段,而且還可以幫助程序員找到解決問(wèn)題的方法.對(duì)于
Linux程序員來(lái)講,GDB(GNU Debugger)通過(guò)與GCC的配合使用,為基于Linux的軟件開發(fā)提供了一個(gè)完善的調(diào)試環(huán)境.
默認(rèn)情況下,GCC在編譯時(shí)不會(huì)將調(diào)試符號(hào)插入到生成的二進(jìn)制代碼中,因?yàn)檫@樣會(huì)增加可執(zhí)行文件的大小.如果需要
在編譯時(shí)生成調(diào)試符號(hào)信息,可以使用GCC的-g或者-ggdb選項(xiàng).GCC在產(chǎn)生調(diào)試符號(hào)時(shí),同樣采用了分級(jí)的思路,開發(fā)人
員可以通過(guò)在-g選項(xiàng)后附加數(shù)字1、2或3來(lái)指定在代碼中加入調(diào)試信息的多少.默認(rèn)的級(jí)別是2(-g2),此時(shí)產(chǎn)生的調(diào)試
信息包括擴(kuò)展的符號(hào)表、行號(hào)、局部或外部變量信息.級(jí)別3(-g3)包含級(jí)別2中的所有調(diào)試信息,以及源代碼中定義的
宏.級(jí)別1(-g1)不包含局部變量和與行號(hào)有關(guān)的調(diào)試信息,因此只能夠用于回溯跟蹤和堆棧轉(zhuǎn)儲(chǔ)之用.回溯跟蹤指的是
監(jiān)視程序在運(yùn)行過(guò)程中的函數(shù)調(diào)用歷史,堆棧轉(zhuǎn)儲(chǔ)則是一種以原始的十六進(jìn)制格式保存程序執(zhí)行環(huán)境的方法,兩者都
是經(jīng)常用到的調(diào)試手段.
GCC產(chǎn)生的調(diào)試符號(hào)具有普遍的適應(yīng)性,可以被許多調(diào)試器加以利用,但如果使用的是GDB,那么還可以通過(guò)-ggdb選項(xiàng)
在生成的二進(jìn)制代碼中包含GDB專用的調(diào)試信息.這種做法的優(yōu)點(diǎn)是可以方便GDB的調(diào)試工作,但缺點(diǎn)是可能導(dǎo)致其它
調(diào)試器(如DBX)無(wú)法進(jìn)行正常的調(diào)試.選項(xiàng)-ggdb能夠接受的調(diào)試級(jí)別和-g是完全一樣的,它們對(duì)輸出的調(diào)試符號(hào)有著
相同的影響.
需要注意的是,使用任何一個(gè)調(diào)試選項(xiàng)都會(huì)使最終生成的二進(jìn)制文件的大小急劇增加,同時(shí)增加程序在執(zhí)行時(shí)的開銷,
因此調(diào)試選項(xiàng)通常僅在軟件的開發(fā)和調(diào)試階段使用.調(diào)試選項(xiàng)對(duì)生成代碼大小的影響從下面的對(duì)比過(guò)程中可以看出來(lái)
:
# gcc optimize.c -o optimize
# ls optimize -l
-rwxrwxr-x 1 xiaowp xiaowp 11649 Nov 20 08:53 optimize (未加調(diào)試選項(xiàng))
# gcc -g optimize.c -o optimize
# ls optimize -l
-rwxrwxr-x 1 xiaowp xiaowp 15889 Nov 20 08:54 optimize (加入調(diào)試選項(xiàng))
雖然調(diào)試選項(xiàng)會(huì)增加文件的大小,但事實(shí)上Linux中的許多軟件在測(cè)試版本甚至最終發(fā)行版本中仍然使用了調(diào)試選項(xiàng)
來(lái)進(jìn)行編譯,這樣做的目的是鼓勵(lì)用戶在發(fā)現(xiàn)問(wèn)題時(shí)自己動(dòng)手解決,是Linux的一個(gè)顯著特色.
為調(diào)試編譯代碼(Compiling Code for Debugging)
為了使 gdb 正常工作, 你必須使你的程序在編譯時(shí)包含調(diào)試信息. 調(diào)試信息包含你程序里的每個(gè)變量的類型和在可
執(zhí)行文件里的地址映射以及源代碼的行號(hào). gdb 利用這些信息使源代碼和機(jī)器碼相關(guān)聯(lián).
◆在編譯時(shí)用 -g 選項(xiàng)打開調(diào)試選項(xiàng).
gdb 基本命令
gdb 支持很多的命令使你能實(shí)現(xiàn)不同的功能. 這些命令從簡(jiǎn)單的文件裝入到允許你檢查所調(diào)用的堆棧內(nèi)容的復(fù)
雜命令, 表27.1列出了你在用 gdb 調(diào)試時(shí)會(huì)用到的一些命令. 想了解 gdb 的詳細(xì)使用請(qǐng)參考 gdb 的指南頁(yè).
表 27.1. 基本 gdb 命令.
命 令 描 述
file 裝入想要調(diào)試的可執(zhí)行文件.
kill 終止正在調(diào)試的程序.
list 列出產(chǎn)生執(zhí)行文件的源代碼的一部分.
next 執(zhí)行一行源代碼但不進(jìn)入函數(shù)內(nèi)部.
step 執(zhí)行一行源代碼而且進(jìn)入函數(shù)內(nèi)部.
run 執(zhí)行當(dāng)前被調(diào)試的程序
quit 終止 gdb
watch 使你能監(jiān)視一個(gè)變量的值而不管它何時(shí)被改變.
break 在代碼里設(shè)置斷點(diǎn), 這將使程序執(zhí)行到這里時(shí)被掛起.
make 使你能不退出 gdb 就可以重新產(chǎn)生可執(zhí)行文件.
shell 使你能不離開 gdb 就執(zhí)行 UNIX shell 命令.
gdb 支持很多與 UNIX shell 程序一樣的命令編輯特征. 你能象在 bash 或 tcsh里那樣按 Tab 鍵讓gdb 幫你補(bǔ)齊
一個(gè)唯一的命令, 如果不唯一的話 gdb 會(huì)列出所有匹配的命令. 你也能用光標(biāo)鍵上下翻動(dòng)歷史命令.
下面還是通過(guò)一個(gè)具體的實(shí)例說(shuō)明如何利用調(diào)試符號(hào)來(lái)分析錯(cuò)誤,所用程序見清單4所示.
清單4:crash.c
#include <stdio.h>
int main(void)
{
int input =0;
printf(Input an integer:);
scanf(%d, input);
printf(The integer you input is %d\\n, input);
return 0;
}
編譯并運(yùn)行上述代碼,會(huì)產(chǎn)生一個(gè)嚴(yán)重的段錯(cuò)誤(Segmentation fault)如下:
# gcc -g crash.c -o crash
# ./crash
Input an integer:10
Segmentation fault
為了更快速地發(fā)現(xiàn)錯(cuò)誤所在,可以使用GDB進(jìn)行跟蹤調(diào)試,方法如下:
# gdb crash
GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)
……
(gdb)
當(dāng)GDB提示符出現(xiàn)的時(shí)候,表明GDB已經(jīng)做好準(zhǔn)備進(jìn)行調(diào)試了,現(xiàn)在可以通過(guò)run命令讓程序開始在GDB的監(jiān)控下運(yùn)行:
(gdb) run
Starting program: /home/xiaowp/thesis/gcc/code/crash
Input an integer:10
Program received signal SIGSEGV, Segmentation fault.
0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6
仔細(xì)分析一下GDB給出的輸出結(jié)果不難看出,程序是由于段錯(cuò)誤而導(dǎo)致異常中止的,說(shuō)明內(nèi)存操作出了問(wèn)題,具體發(fā)生
問(wèn)題的地方是在調(diào)用_IO_vfscanf_internal ( )的時(shí)候.為了得到更加有價(jià)值的信息,可以使用GDB提供的回溯跟蹤命
令backtrace,執(zhí)行結(jié)果如下:
(gdb) backtrace
#0 0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6
#1 0xbffff0c0 in ?? ()
#2 0x4008e0ba in scanf () from /lib/libc.so.6
#3 0x08048393 in main () at crash.c:11
#4 0x40042917 in __libc_start_main () from /lib/libc.so.6
跳過(guò)輸出結(jié)果中的前面三行,從輸出結(jié)果的第四行中不難看出,GDB已經(jīng)將錯(cuò)誤定位到crash.c中的第11行了.現(xiàn)在仔細(xì)
檢查一下:
(gdb) frame 3
#3 0x08048393 in main () at crash.c:11
11 scanf(%d, input);
使用GDB提供的frame命令可以定位到發(fā)生錯(cuò)誤的代碼段,該命令后面跟著的數(shù)值可以在backtrace命令輸出結(jié)果中的
行首找到.現(xiàn)在已經(jīng)發(fā)現(xiàn)錯(cuò)誤所在了,應(yīng)該將
scanf(%d, input);
改為
scanf(%d, &input);
完成后就可以退出GDB了,命令如下:
(gdb) quit
GDB的功能遠(yuǎn)遠(yuǎn)不止如此,它還可以單步跟蹤程序、檢查內(nèi)存變量和設(shè)置斷點(diǎn)等.
調(diào)試時(shí)可能會(huì)需要用到編譯器產(chǎn)生的中間結(jié)果,這時(shí)可以使用-save-temps選項(xiàng),讓GCC將預(yù)處理代碼、匯編代碼和目
標(biāo)代碼都作為文件保存起來(lái).如果想檢查生成的代碼是否能夠通過(guò)手工調(diào)整的辦法來(lái)提高執(zhí)行性能,在編譯過(guò)程中生
成的中間文件將會(huì)很有幫助,具體情況如下:
# gcc -save-temps foo.c -o foo
# ls foo*
foo foo.c foo.i foo.s
GCC支持的其它調(diào)試選項(xiàng)還包括-p和-pg,它們會(huì)將剖析(Profiling)信息加入到最終生成的二進(jìn)制代碼中.剖析信息對(duì)
于找出程序的性能瓶頸很有幫助,是協(xié)助Linux程序員開發(fā)出高性能程序的有力工具.在編譯時(shí)加入-p選項(xiàng)會(huì)在生成的
代碼中加入通用剖析工具(Prof)能夠識(shí)別的統(tǒng)計(jì)信息,而-pg選項(xiàng)則生成只有GNU剖析工具(Gprof)才能識(shí)別的統(tǒng)計(jì)信
息.
最后提醒一點(diǎn),雖然GCC允許在優(yōu)化的同時(shí)加入調(diào)試符號(hào)信息,但優(yōu)化后的代碼對(duì)于調(diào)試本身而言將是一個(gè)很大的挑戰(zhàn)
.代碼在經(jīng)過(guò)優(yōu)化之后,在源程序中聲明和使用的變量很可能不再使用,控制流也可能會(huì)突然跳轉(zhuǎn)到意外的地方,循環(huán)
語(yǔ)句有可能因?yàn)檠h(huán)展開而變得到處都有,所有這些對(duì)調(diào)試來(lái)講都將是一場(chǎng)噩夢(mèng).建議在調(diào)試的時(shí)候最好不使用任何
優(yōu)化選項(xiàng),只有當(dāng)程序在最終發(fā)行的時(shí)候才考慮對(duì)其進(jìn)行優(yōu)化.
上次的培訓(xùn)園地中介紹了GCC的編譯過(guò)程、警告提示功能、庫(kù)依賴、代碼優(yōu)化和程序調(diào)試六個(gè)方面的內(nèi)容.這期是最
后的一部分內(nèi)容.
加速
在將源代碼變成可執(zhí)行文件的過(guò)程中,需要經(jīng)過(guò)許多中間步驟,包含預(yù)處理、編譯、匯編和連接.這些過(guò)程實(shí)際上是由
不同的程序負(fù)責(zé)完成的.大多數(shù)情況下GCC可以為L(zhǎng)inux程序員完成所有的后臺(tái)工作,自動(dòng)調(diào)用相應(yīng)程序進(jìn)行處理.
這樣做有一個(gè)很明顯的缺點(diǎn),就是GCC在處理每一個(gè)源文件時(shí),最終都需要生成好幾個(gè)臨時(shí)文件才能完成相應(yīng)的工作,
從而無(wú)形中導(dǎo)致處理速度變慢.例如,GCC在處理一個(gè)源文件時(shí),可能需要一個(gè)臨時(shí)文件來(lái)保存預(yù)處理的輸出、一個(gè)臨
時(shí)文件來(lái)保存編譯器的輸出、一個(gè)臨時(shí)文件來(lái)保存匯編器的輸出,而讀寫這些臨時(shí)文件顯然需要耗費(fèi)一定的時(shí)間.當(dāng)
軟件項(xiàng)目變得非常龐大的時(shí)候,花費(fèi)在這上面的代價(jià)可能會(huì)變得很沉重.
解決的辦法是,使用Linux提供的一種更加高效的通信方式—管道.它可以用來(lái)同時(shí)連接兩個(gè)程序,其中一個(gè)程序的輸
出將被直接作為另一個(gè)程序的輸入,這樣就可以避免使用臨時(shí)文件,但編譯時(shí)卻需要消耗更多的內(nèi)存.
在編譯過(guò)程中使用管道是由GCC的-pipe選項(xiàng)決定的.下面的這條命令就是借助GCC的管道功能來(lái)提高編譯速度的:
# gcc -pipe foo.c -o foo
在編譯小型工程時(shí)使用管道,編譯時(shí)間上的差異可能還不是很明顯,但在源代碼非常多的大型工程中,差異將變得非常
明顯.
文件擴(kuò)展名
在使用GCC的過(guò)程中,用戶對(duì)一些常用的擴(kuò)展名一定要熟悉,并知道其含義.為了方便大家學(xué)習(xí)使用GCC,在此將這些擴(kuò)
展名羅列如下:
.c C原始程序;
.C C++原始程序;
.cc C++原始程序;
.cxx C++原始程序;
.m Objective-C原始程序;
.i 已經(jīng)過(guò)預(yù)處理的C原始程序;
.ii 已經(jīng)過(guò)預(yù)處理之C++原始程序;
.s 組合語(yǔ)言原始程序;
.S 組合語(yǔ)言原始程序;
.h 預(yù)處理文件(標(biāo)頭文件);
.o 目標(biāo)文件;
.a 存檔文件.
GCC常用選項(xiàng)
GCC作為L(zhǎng)inux下C/C++重要的編譯環(huán)境,功能強(qiáng)大,編譯選項(xiàng)繁多.為了方便大家日后編譯方便,在此將常用的選項(xiàng)及說(shuō)
明羅列出來(lái)如下:
-c 通知GCC取消鏈接步驟,即編譯源碼并在最后生成目標(biāo)文件;
-Dmacro 定義指定的宏,使它能夠通過(guò)源碼中的#ifdef進(jìn)行檢驗(yàn);
-E 不經(jīng)過(guò)編譯預(yù)處理程序的輸出而輸送至標(biāo)準(zhǔn)輸出;
-g3 獲得有關(guān)調(diào)試程序的詳細(xì)信息,它不能與-o選項(xiàng)聯(lián)合使用;
-Idirectory 在包含文件搜索路徑的起點(diǎn)處添加指定目錄;
-llibrary 提示鏈接程序在創(chuàng)建最終可執(zhí)行文件時(shí)包含指定的庫(kù);
-O、-O2、-O3 將優(yōu)化狀態(tài)打開,該選項(xiàng)不能與-g選項(xiàng)聯(lián)合使用;
-S 要求編譯程序生成來(lái)自源代碼的匯編程序輸出;
-v 啟動(dòng)所有警報(bào);
-Wall 在發(fā)生警報(bào)時(shí)取消編譯操作,即將警報(bào)看作是錯(cuò)誤;
-Werror 在發(fā)生警報(bào)時(shí)取消編譯操作,即把報(bào)警當(dāng)作是錯(cuò)誤;
-w 禁止所有的報(bào)警.
小結(jié)
GCC是在Linux下開發(fā)程序時(shí)必須掌握的工具之一.本文對(duì)GCC做了一個(gè)簡(jiǎn)要的介紹,主要講述了如何使用GCC編譯程序
、產(chǎn)生警告信息、調(diào)試程序和加快GCC的編譯速度.對(duì)所有希望早日跨入Linux開發(fā)者行列的人來(lái)說(shuō),GCC就是成為一名
優(yōu)秀的Linux程序員的起跑線.
http://delia.org.ru/ArticleView/2005-9-7/Article_View_131482.Htm
posted on 2006-02-13 16:12 bluesky 閱讀(2205) 評(píng)論(0) 編輯 收藏 所屬分類: C/C++