
?? ? ? ?這是一份非常簡(jiǎn)短的文檔,可以幫助你熟悉UNIX系統(tǒng)上C編程環(huán)境的基礎(chǔ)知識(shí)。它不是面面俱到或特別詳細(xì),只是給你足夠的知識(shí)讓你繼續(xù)學(xué)習(xí)。
? ? ? 關(guān)于編程的幾點(diǎn)一般建議:如果想成為一名專業(yè)程序員,需要掌握的不僅僅是語(yǔ)言的語(yǔ)法。具體來(lái)說(shuō),應(yīng)該了解你的工具,了解你的庫(kù),并了解你的文檔。與C編譯相關(guān)的工具是gcc、gdb和ld。還有大量的庫(kù)函數(shù)也可供你使用,但幸運(yùn)的是libc包含了許多功能,默認(rèn)情況下它與所有C程序相關(guān)聯(lián)——需要做的就是包含正確的頭文件。最后,了解如何找到所需的庫(kù)函數(shù)(例如,學(xué)習(xí)查找和閱讀手冊(cè)頁(yè))是一項(xiàng)值得掌握的技能。我們稍后將更詳細(xì)地討論這些內(nèi)容。
? ? ? 就像生活中(幾乎)所有值得做的事情,成為這些領(lǐng)域的專家需要時(shí)間——事先花時(shí)間了解有關(guān)工具和環(huán)境的更多信息,絕對(duì)值得付出努力。
? ? ? E.1 一個(gè)簡(jiǎn)單的C程序
? ? ? 我們從一個(gè)簡(jiǎn)單的C程序開(kāi)始,它保存在文件“hw.c”中。與Java不同,文件名和文件內(nèi)容之間不一定有關(guān)系。因此,請(qǐng)以適當(dāng)?shù)姆绞?,利用你的常識(shí)來(lái)命名文件。
? ? ? 第一行指定要包含的文件,在本例中為stdio.h,它包含許多常用輸入/輸出函數(shù)的“原型”。我們感興趣的是printf()。當(dāng)你使用#include指令時(shí),就告訴C預(yù)處理器(cpp)查找特定文件(例如,stdio.h),并將其直接插入到#include的代碼中。默認(rèn)情況下,cpp將查看目錄/usr/include/,嘗試查找該文件。
? ? ? 下面一部分指定main()函數(shù)的簽名,即它返回一個(gè)整數(shù)(int),并用兩個(gè)參數(shù)來(lái)調(diào)用,一個(gè)整數(shù)argc,它是命令行上參數(shù)數(shù)量的計(jì)數(shù)。一個(gè)指向字符(argv)的指針數(shù)組,每個(gè)指針都包含命令行中的一個(gè)單詞,最后一個(gè)單詞為null。下面的指針和數(shù)組會(huì)更多。
? /* header files go up here */
? /* note that C comments are enclosed within a slash and a star, and
? may wrap over lines */
? // if you use gcc, two slashes will work too (and may be preferred)
? #include <stdio.h>
? /* main returns an integer */
? int main(int argc, char *argv[]) {
? /* printf is our output function;
? by default, writes to standard out */
? /* printf returns an integer, but we ignore that */
? printf("hello, world\n");
? /* return 0 to indicate all went well */
? return(0);
? }
? ? ? 程序然后簡(jiǎn)單打印字符串“hello,world”,并將輸出流換到下一行,這是由printf()調(diào)用結(jié)束時(shí)的“\n”實(shí)現(xiàn)的。然后,程序返回一個(gè)值并結(jié)束,該值被傳遞回執(zhí)行程序的shell。終端上的腳本或用戶可以檢查此值(在csh和tcsh shell中,它存儲(chǔ)在狀態(tài)變量中),以查看程序是干凈地退出還是出錯(cuò)。
? ? ? E.2 編譯和執(zhí)行
? ? ? 我們現(xiàn)在將學(xué)習(xí)如何編譯程序。請(qǐng)注意,我們將使用gcc作為示例,但在某些平臺(tái)上,可以使用不同的(本機(jī))編譯器cc。
? ? ? 在shell提示符下,只需鍵入:
? prompt> gcc hw.c
? gcc不是真正的編譯器,而是所謂的“編譯器驅(qū)動(dòng)程序”,因此它協(xié)調(diào)了編譯的許多步驟。通常有4~5個(gè)步驟。首先,gcc將執(zhí)行cpp(C預(yù)處理器)來(lái)處理某些指令(例如#define和#include。程序cpp只是一個(gè)源到源的轉(zhuǎn)換器,所以它的最終產(chǎn)品仍然只是源代碼( 即一個(gè)C文件)。然后真正的編譯將開(kāi)始,通常是一個(gè)名為cc1的命令。這會(huì)將源代碼級(jí)別的C代碼轉(zhuǎn)換為特定主機(jī)的低級(jí)匯編代碼。然后執(zhí)行匯編程序as,生成目標(biāo)代碼(機(jī)器可以真正理解的數(shù)據(jù)位和代碼位),最后鏈接編輯器(或鏈接器)ld將它們組合成最終的可執(zhí)行程序。幸運(yùn)的是(?。?,在大多數(shù)情況下,你可以不明白gcc如何工作,只需愉快地使用正確的標(biāo)志。
? ? ? 上面編譯的結(jié)果是一個(gè)可執(zhí)行文件,命名為(默認(rèn)情況下)a.out。然后運(yùn)行該程序,只需鍵入:
? prompt> ./a.out
? ? ? 運(yùn)行該程序時(shí),操作系統(tǒng)將正確設(shè)置argc和argv,以便程序可以根據(jù)需要處理命令行參數(shù)。具體來(lái)說(shuō),argc將等于1,argv [0]將是字符串“./a.out”,而argv[1]將是null,表示數(shù)組的結(jié)束。
? ? ? E.3 有用的標(biāo)志
? ? ? 在繼續(xù)使用C語(yǔ)言之前,我們首先指出一些有用的gcc編譯標(biāo)志。
? prompt> gcc -o hw hw.c # -o: to specify the executable name
? prompt> gcc -Wall hw.c # -Wall: gives much better warnings
? prompt> gcc -g hw.c # -g: to enable debugging with gdb
? prompt> gcc -O hw.c # -O: to turn on optimization
? ? ? 當(dāng)然,你可以根據(jù)需要組合這些標(biāo)志(例如gcc -o hw -g -Wall hw.c)。在這些標(biāo)志中,你應(yīng)該總是使用-Wall,這會(huì)提供很多關(guān)于可能出錯(cuò)的額外警告。不要忽視警告!相反,要修復(fù)它們,讓它們幸福地消失。
? ? ? E.4 與庫(kù)鏈接
? ? ? 有時(shí),你可能想在程序中使用庫(kù)函數(shù)。因?yàn)镃庫(kù)中有很多函數(shù)(可以自動(dòng)鏈接到每個(gè)程序),所以通常要做的就是找到正確的#include文件。最好的方法是通過(guò)手冊(cè)頁(yè)(manual page),通常稱為man page。
? ? ? 例如,假設(shè)你要使用fork()系統(tǒng)調(diào)用[1]。在shell提示符下輸入man fork,你將獲得fork()如何工作的文本描述。最頂部的是一個(gè)簡(jiǎn)短的代碼片段,它告訴你在程序中需要#include哪些文件才能讓它通過(guò)編譯。對(duì)于fork(),需要#include sys/types.h和unistd.h,按如下方式完成:
? #include <sys/types.h>
? #include <unistd.h>
? ? ? 但是,某些庫(kù)函數(shù)不在C庫(kù)中,因此你必須做更多的工作。例如,數(shù)學(xué)庫(kù)有許多有用的函數(shù)(正弦、余弦、正切等)。如果要在代碼中包含函數(shù)tan(),應(yīng)該先查手冊(cè)頁(yè)。在tan的Linux手冊(cè)頁(yè)的頂部,你會(huì)看到以下兩行代碼:
? #include <math.h>
? ...
? Link with -lm.
? ? 你已經(jīng)應(yīng)該理解了第一行——你需要#include數(shù)學(xué)庫(kù),它位于文件系統(tǒng)的標(biāo)準(zhǔn)位置(即/usr/include/math.h)。但是,下一行告訴你如何將程序與數(shù)學(xué)庫(kù)“鏈接”。有許多有用的庫(kù)可以鏈接。其中許多都放在/usr/lib中,數(shù)學(xué)庫(kù)也確實(shí)在這里。
? ? ? 有兩種類型的庫(kù):靜態(tài)鏈接庫(kù)(以.a結(jié)尾)和動(dòng)態(tài)鏈接庫(kù)(以.so結(jié)尾)。靜態(tài)鏈接庫(kù)直接組合到可執(zhí)行文件中。也就是說(shuō),鏈接器將庫(kù)的低級(jí)代碼插入到可執(zhí)行文件中,從而產(chǎn)生更大的二進(jìn)制對(duì)象。動(dòng)態(tài)鏈接通過(guò)在程序可執(zhí)行文件中包含對(duì)庫(kù)的引用來(lái)改進(jìn)這一點(diǎn)。程序運(yùn)行時(shí),操作系統(tǒng)加載程序動(dòng)態(tài)鏈接庫(kù)。這種方法優(yōu)于靜態(tài)方法,因?yàn)樗?jié)省了磁盤(pán)空間(沒(méi)有不必要的大型可執(zhí)行文件),并允許應(yīng)用程序在內(nèi)存中共享庫(kù)代碼和靜態(tài)數(shù)據(jù)。對(duì)于數(shù)學(xué)庫(kù),靜態(tài)和動(dòng)態(tài)版本都可用,靜態(tài)版本是/usr/lib/libm.a,動(dòng)態(tài)版本是/usr/lib/libm.so。小編是一個(gè)有著10年開(kāi)發(fā)經(jīng)驗(yàn)的C++程序員,關(guān)于C++,自己有做材料的整合,一個(gè)完整的學(xué)習(xí)C++的路線,學(xué)習(xí)材料和工具給大家!希望你也能憑自己的努力,成為下一個(gè)優(yōu)秀的程序員。
? ? ? 在任何情況下,要與數(shù)學(xué)庫(kù)鏈接,都需要向鏈接編輯器指定庫(kù)。這可以通過(guò)使用正確的標(biāo)志調(diào)用gcc來(lái)實(shí)現(xiàn)。
? prompt> gcc -o hw hw.c -Wall -lm
? ? ? -l×××標(biāo)志告訴鏈接器查找lib×××.so或lib×××.a,可能按此順序。如果出于某種原因,你堅(jiān)持使用動(dòng)態(tài)庫(kù)而不是靜態(tài)庫(kù),那么可以使用另一個(gè)標(biāo)志——看看你是否能找到它是什么。人們有時(shí)更喜歡庫(kù)的靜態(tài)版本,因?yàn)槭褂脛?dòng)態(tài)庫(kù)有一點(diǎn)點(diǎn)性能開(kāi)銷。
? ? ? 最后要注意:如果你希望編譯器在不同于常用位置的路徑中搜索頭文件,或者希望它與你指定的庫(kù)鏈接,可以使用編譯器標(biāo)志-I /foo/bar,來(lái)查找目錄/foo/ bar中的頭文件,使用-L /foo/bar標(biāo)志來(lái)查找/foo/bar目錄中的庫(kù)。以這種方式指定的一個(gè)常用目錄是“.”(稱為“點(diǎn)”),它是UNIX中當(dāng)前目錄的簡(jiǎn)寫(xiě)。
? ? ? 請(qǐng)注意,-I標(biāo)志應(yīng)該針對(duì)編譯,而-L標(biāo)志針對(duì)鏈接。
? ? ? E.5 分別編譯
? ? ? 一旦程序開(kāi)始變得足夠大,你可能希望將其拆分為單獨(dú)的文件,分別編譯每個(gè)文件,然后將它們鏈接在一起。例如,假設(shè)你有兩個(gè)文件,hw.c和helper.c,希望單獨(dú)編譯它們,然后將它們鏈接在一起。
? # we are using -Wall for warnings, -O for optimization
? prompt> gcc -Wall -O -c hw.c
? prompt> gcc -Wall -O -c helper.c
? prompt> gcc -o hw hw.o helper.o -lm
? ? ? -c標(biāo)志告訴編譯器只是生成一個(gè)目標(biāo)文件——在本例中是名為hw.o和helper.o的文件。這些文件不是可執(zhí)行文件,而只是每個(gè)源文件中代碼的機(jī)器代碼表示。要將目標(biāo)文件組合成可執(zhí)行文件,必須將它們“鏈接”在一起。這是通過(guò)第三行g(shù)cc -o hw hw.o helper.o)完成的。在這種情況下,gcc看到指定的輸入文件不是源文件(.c),而是目標(biāo)文件(.o),因此直接跳到最后一步,調(diào)用鏈接編輯器ld將它們鏈接到一起,得到單個(gè)可執(zhí)行文件。由于它的功能,這行通常被稱為“鏈接行”,并且可以指定特定的鏈接命令,例如-lm。類似地,僅在編譯階段需要的標(biāo)志,諸如-Wall和-O,就不需要包含在鏈接行上,只是包含在編譯行上。
? ? ? 當(dāng)然,你可以在一行中為gcc指定所有C源文件(gcc -Wall -O -o hw hw.c helper.c),但這需要系統(tǒng)重新編譯每個(gè)源代碼文件,這個(gè)過(guò)程可能很耗時(shí)。通過(guò)單獨(dú)編譯每個(gè)源文件,你只需重新編譯編輯修改過(guò)的文件,從而節(jié)省時(shí)間,提高工作效率。這個(gè)過(guò)程最好由另一個(gè)程序make來(lái)管理,我們接下來(lái)介紹它。
? ? ? E.6 Makefile文件
? ? ? 程序make讓你自動(dòng)化大部分構(gòu)建過(guò)程,因此對(duì)于任何認(rèn)真的程序(和程序員)來(lái)說(shuō),都是一個(gè)至關(guān)重要的工具。來(lái)看一個(gè)簡(jiǎn)單的例子,它保存在名為Makefile的文件。
? ? ? 要構(gòu)建程序,只需輸入:
? prompt> make
? ? ? 這會(huì)(默認(rèn))查找Makefile或makefile,將其作為輸入(你可以用標(biāo)志指定不同的makefile,閱讀手冊(cè)頁(yè),找出是哪個(gè)標(biāo)志)。gmake是make的gnu版本,比傳統(tǒng)的make功能更多,所以我們將在下面的部分中重點(diǎn)介紹它(盡管我們互換使用這兩個(gè)詞)。這些討論大多數(shù)都基于gmake的info頁(yè)面,要了解如何查找這些頁(yè)面,請(qǐng)參閱“E.8文檔”部分。另外請(qǐng)注意:在Linux系統(tǒng)上,gmake和make是一樣的。
? hw: hw.o helper.o
? gcc -o hw hw.o helper.o -lm
? hw.o: hw.c
? gcc -O -Wall -c hw.c
? helper.o: helper.c
? gcc -O -Wall -c helper.c
? clean:
? rm -f hw.o helper.o hw
? ? ? Makefile基于規(guī)則,這些規(guī)則決定需要發(fā)生的事情。規(guī)則的一般形式是:
? target: prerequisite1 prerequisite2 ...
? command1
? command2
? ...
? ? ? target(目標(biāo))通常是程序生成的文件的名稱。目標(biāo)的例子是可執(zhí)行文件或目標(biāo)文件。目標(biāo)也可以是要執(zhí)行的操作的名稱,例如在我們的示例中為“clean”。
? ? ? prerequisite(先決條件)是用于生成目標(biāo)的輸入文件。目標(biāo)通常依賴于幾個(gè)文件。例如,要構(gòu)建可執(zhí)行文件hw,需要首先構(gòu)建兩個(gè)目標(biāo)文件:hw.o和helper.o。
? ? ? 最后,command(命令)是一個(gè)執(zhí)行的動(dòng)作。一條規(guī)則可能有多個(gè)命令,每個(gè)命令都在自己的行上。重要提示:必須在每個(gè)命令行的開(kāi)頭放一個(gè)制表符!如果你只放空格,make會(huì)打印出一些含糊的錯(cuò)誤信息并退出。
? ? ? 通常,命令在具有先決條件的規(guī)則中,如果任何先決條件發(fā)生更改,就要重新創(chuàng)建目標(biāo)文件。但是,為目標(biāo)指定命令的規(guī)則不需要先決條件。例如,包含delete命令、與目標(biāo)“clean”相關(guān)的規(guī)則中,沒(méi)有先決條件。
? ? ? 回到我們的例子,在執(zhí)行make時(shí),大致工作如下:首先,看到目標(biāo)hw,并且意識(shí)到要構(gòu)建它,它必須具備兩個(gè)先決條件,hw.o和helper.o。因此,hw依賴于這兩個(gè)目標(biāo)文件。然后,Make將檢查每個(gè)目標(biāo)。在檢查hw.o時(shí),看到它取決于hw.c。這是關(guān)鍵:如果hw.c最近被修改,但hw.o沒(méi)有被創(chuàng)建,make會(huì)知道hw.o已經(jīng)過(guò)時(shí),應(yīng)該重新生成。在這種情況下,會(huì)執(zhí)行命令行g(shù)cc -O -Wall -c hw.c,生成hw.o。因此,如果你正在編譯大型程序,make會(huì)知道哪些目標(biāo)文件需要根據(jù)其依賴項(xiàng)重新生成,并且只會(huì)執(zhí)行必要的工作來(lái)重新創(chuàng)建可執(zhí)行文件。另外請(qǐng)注意,如果hw.o根本不存在,也會(huì)被創(chuàng)建。
? ? ? 繼續(xù),基于上面定義的相同標(biāo)準(zhǔn),helper.o也會(huì)重新生成或創(chuàng)建。當(dāng)兩個(gè)目標(biāo)文件都已創(chuàng)建時(shí),make現(xiàn)在可以執(zhí)行命令來(lái)創(chuàng)建最終的可執(zhí)行文件,然后返回并執(zhí)行以下操作:gcc -o hw hw.o helper.o -lm。
? ? ? 到目前為止,我們一直沒(méi)提makefile中的clean目標(biāo)。
? ? ? 要使用它,必須明確提出要求,鍵入以下代碼:
? prompt> make clean
? ? ? 這會(huì)在命令行上執(zhí)行該命令。因?yàn)閏lean目標(biāo)沒(méi)有先決條件,所以輸入make clean將導(dǎo)致命令被執(zhí)行。在這種情況下,clean目標(biāo)用于刪除目標(biāo)文件和可執(zhí)行文件,如果你希望從頭開(kāi)始重建整個(gè)程序,就非常方便。
? ? ? 現(xiàn)在你可能會(huì)想,“好吧,這似乎沒(méi)問(wèn)題,但這些makefile確實(shí)很麻煩!”你說(shuō)得對(duì)——如果它們總是這樣寫(xiě)的話。幸運(yùn)的是,有很多快捷方式,讓使用更容易。例如,這個(gè)makefile具有相同的功能,但用起來(lái)更好:
? # specify all source files here
? SRCS = hw.c helper.c
? # specify target here (name of executable)
? TARG = hw
? # specify compiler, compile flags, and needed libs
? CC = gcc
? OPTS = -Wall -O
? LIBS = -lm
? # this translates .c files in src list to .o’s
? OBJS = $(SRCS:.c=.o)
? # all is not really needed, but is used to generate the target
? all: $(TARG)
? # this generates the target executable
? $(TARG): $(OBJS)
? $(CC) -o $(TARG) $(OBJS) $(LIBS)
? # this is a generic rule for .o files
? %.o: %.c
? $(CC) $(OPTS) -c $< -o $@
? # and finally, a clean line
? clean:
? rm -f $(OBJS) $(TARG)
? ? ? 雖然我們不會(huì)詳細(xì)介紹make語(yǔ)法,但如你所見(jiàn),這個(gè)makefile可以讓生活更輕松一些。例如,它允許你輕松地將新的源文件添加到構(gòu)建中,只需將它們加入makefile頂部的SRCS變量即可。你還可以通過(guò)更改TARG行輕松更改可執(zhí)行文件的名稱,并且可以輕松修改指定編譯器,標(biāo)志和庫(kù)。
? ? ? 關(guān)于make的最后一句話:找出目標(biāo)的先決條件并非總是很容易,特別是在大型復(fù)雜程序中。毫不奇怪,有另一種工具可以幫助解決這個(gè)問(wèn)題,稱為makedepend。自己閱讀它,看看是否可以將它合并到一個(gè)makefile中。
? ? ? E.7 調(diào)試
? ? ? 最后,在創(chuàng)建了良好的構(gòu)建環(huán)境和正確編譯的程序之后,你可能會(huì)發(fā)現(xiàn)程序有問(wèn)題。解決問(wèn)題的一種方法是認(rèn)真思考——這種方法有時(shí)會(huì)成功,但往往不會(huì)。問(wèn)題是缺乏信息。你只是不知道程序中到底發(fā)生了什么,因此無(wú)法弄清楚為什么它沒(méi)有按預(yù)期運(yùn)行。幸運(yùn)的是,有某種幫助工具:gdb,GNU調(diào)試器。
? ? ? 將以下錯(cuò)誤代碼保存在buggy.c文件中,然后編譯成可執(zhí)行文件。
? #include <stdio.h>
? struct Data {
? int x;
? };
? int
? main(int argc, char *argv[])
? {
? struct Data *p = NULL;
? printf("%d\n", p->x);
? }
? ? ? 在這個(gè)例子中,主程序在變量p為NULL時(shí)引用它,這將導(dǎo)致分段錯(cuò)誤。當(dāng)然,這個(gè)問(wèn)題應(yīng)該很容易通過(guò)檢查來(lái)解決,但在更復(fù)雜的程序中,找到這樣的問(wèn)題并非總是那么容易。
? ? ? 要為調(diào)試會(huì)話做好準(zhǔn)備,請(qǐng)重新編譯程序,并確保將-g標(biāo)志加入每個(gè)編譯行。這讓可執(zhí)行文件包含額外的調(diào)試信息,這些信息在調(diào)試會(huì)話期間非常有用。另外,不要打開(kāi)優(yōu)化(-O)。盡管這可能也行,但在調(diào)試過(guò)程中也可能導(dǎo)致困擾。
? ? ? 使用-g重新編譯后,你就可以使用調(diào)試器了。在命令提示符處啟動(dòng)gdb,如下所示:
? prompt> gdb buggy
? ? ? 這讓你進(jìn)入與調(diào)試器的交互式會(huì)話。請(qǐng)注意,你還可以使用調(diào)試器來(lái)檢查在錯(cuò)誤運(yùn)行期間生成的“核心”文件,或者連上已在運(yùn)行的程序。閱讀文檔以了解更多相關(guān)信息。
? ? ? 進(jìn)入調(diào)試器后,你可能會(huì)看到以下內(nèi)容:
? prompt> gdb buggy
? GNU gdb ...
? Copyright 2008 Free Software Foundation, Inc.
? (gdb)
? ? ? 你可能想要做的第一件事就是繼續(xù)運(yùn)行程序。這只需在gdb命令提示符下輸入run。在這個(gè)例子中,你可能會(huì)看到:
? (gdb) run
? Starting program: buggy
? Program received signal SIGSEGV, Segmentation fault.
? 0x8048433 in main (argc=1, argv=0xbffff844) at buggy.cc:19
? 19 printf("%d\n", p->x);
? ? ? 從示例中可以看出,在這種情況下,gdb會(huì)立即指出問(wèn)題發(fā)生的位置。在我們嘗試引用p的行中產(chǎn)生了“分段錯(cuò)誤”。這就意味著我們?cè)L問(wèn)了一些我們不應(yīng)該訪問(wèn)的內(nèi)存。這時(shí),精明的程序員可以檢查代碼,然后說(shuō)“啊哈!肯定是p沒(méi)有指向任何有效的地址,因此不應(yīng)該引用!”,然后繼續(xù)修復(fù)該問(wèn)題。
? ? ? 但是,如果你不知道發(fā)生了什么,可能想要檢查一些變量。gdb允許你在調(diào)試會(huì)話期間以交互方式執(zhí)行此操作。
? (gdb) print p
? 1 = (Data *) 0x0
? ? ? 通過(guò)使用print原語(yǔ),我們可以檢查p,并看到它是指向Data類型結(jié)構(gòu)的指針,并且它當(dāng)前設(shè)置為NULL(即零,即十六進(jìn)制零,此處顯示為“0x0”)。
? ? ? 最后,你還可以在程序中設(shè)置斷點(diǎn),讓調(diào)試器在某個(gè)函數(shù)中停止程序。執(zhí)行此操作后,單步執(zhí)行(一次一行),看看發(fā)生了什么,這通常很有用。
? (gdb) break main
? Breakpoint 1 at 0x8048426: file buggy.cc, line 17.
? (gdb) run
? Starting program: /homes/hacker/buggy
? Breakpoint 1, main (argc=1, argv=0xbffff844) at buggy.cc:17
? 17 struct Data *p = NULL;
? (gdb) next
? 19 printf("%d\n", p->x);
? (gdb)
? Program received signal SIGSEGV, Segmentation fault.
? 0x8048433 in main (argc=1, argv=0xbffff844) at buggy.cc:19
? 19 printf("%d\n", p->x);
? ? ? 在上面的例子中,在main()函數(shù)中設(shè)置了斷點(diǎn)。因此,當(dāng)我們運(yùn)行程序時(shí),調(diào)試器幾乎立即停止在main執(zhí)行。在示例中的該點(diǎn)處,發(fā)出“next”命令,它將執(zhí)行下一行源代碼級(jí)指令?!皀ext”和“step”都是繼續(xù)執(zhí)行程序的有用方法——在文檔中閱讀,以獲取更多詳細(xì)信息[2]。
? ? ? 這里的討論真的對(duì)gdb不公平,它是豐富而靈活的調(diào)試工具,有許多功能,而不只是這里有限篇幅中描述的功能。在閑暇之余閱讀更多相關(guān)信息,你將成為一名專家。
? ? ? E.8 文檔
? ? ? 要了解有關(guān)所有這些事情的更多信息,你必須做兩件事:第一是使用這些工具;第二是自己閱讀更多相關(guān)信息。了解更多關(guān)于gcc、gmake和gdb的一種方法是閱讀它們的手冊(cè)頁(yè)。在命令提示符下輸入man gcc、man gmake或man gdb。你還可以使用man -k在手冊(cè)頁(yè)中搜索關(guān)鍵字,但這并非總?cè)缛艘?。谷歌搜索可能是更好的方法?/p>
? ? ? 關(guān)于手冊(cè)頁(yè)有一個(gè)棘手的事情:如果有多個(gè)名為×××的東西,輸入man ×××可能不會(huì)得到你想要的東西。例如,如果你正在尋找kill()系統(tǒng)調(diào)用手冊(cè)頁(yè),如果只是在提示符下鍵入man kill,會(huì)得到錯(cuò)誤的手冊(cè)頁(yè),因?yàn)橛幸粋€(gè)名為kill的命令行程序。手冊(cè)頁(yè)分為幾個(gè)部分(section),默認(rèn)情況下,man將返回找到的最低層部分的手冊(cè)頁(yè),在本例中為第1部分。請(qǐng)注意,你可以通過(guò)查看頁(yè)面的頂部來(lái)確定你看到的手冊(cè)頁(yè):如果看到kill(2),就知道你在第2節(jié)的正確手冊(cè)頁(yè)中,這里放的是系統(tǒng)調(diào)用。有關(guān)手冊(cè)頁(yè)的每個(gè)不同部分中存儲(chǔ)的內(nèi)容,請(qǐng)鍵入man man以了解更多信息。另外請(qǐng)注意,man -a kill可用于遍歷名為“kill”的所有手冊(cè)頁(yè)。
? ? ? 手冊(cè)頁(yè)對(duì)于查找許多內(nèi)容非常有用。特別是,你經(jīng)常需要查找要傳遞給庫(kù)調(diào)用的參數(shù),或者需要包含哪些頭文件才能使用庫(kù)調(diào)用。所有這些都在手冊(cè)頁(yè)中提供。例如,如果查找open()系統(tǒng)調(diào)用,你會(huì)看到:
? SYNOPSIS
? #include <sys/types.h>
? #include <sys/stat.h>
? #include <fcntl.h>
? int open(const char *path, int oflag, /* mode_t mode */...);
? ? ? 這告訴你包含頭文件sys/types.h、sys/stat.h和fcntl.h,以便使用open調(diào)用。它還告訴你要傳遞給open的參數(shù),即名為path的字符串和整數(shù)標(biāo)志oflag,以及指定文件模式的可選參數(shù)。如果你需要鏈接某個(gè)庫(kù)以使用該調(diào)用,這里也會(huì)告訴你。
? ? ? 需要一些努力才能有效使用手冊(cè)頁(yè)。它們通常分為許多標(biāo)準(zhǔn)部分。主體將描述如何傳遞不同的參數(shù),以使函數(shù)具有不同的行為。
? ? ? 一個(gè)特別有用的部分是手冊(cè)頁(yè)的RETURN VALUES部分,它告訴你成功或失敗時(shí)函數(shù)將返回什么。再次引用open()的手冊(cè)頁(yè):
? RETURN VALUES
? Upon successful completion, the open() function opens the
? file and return a non-negative integer representing the
? lowest numbered unused file descriptor. Otherwise, -1 is
? returned, errno is set to indicate the error, and no files
? are created or modified.
? ? ? 因此,通過(guò)檢查open的返回值,你可以看到是否成功打開(kāi)。如果沒(méi)有,open(以及許多標(biāo)準(zhǔn)庫(kù)函數(shù))會(huì)將一個(gè)名為errno的全局變量設(shè)置為一個(gè)值,來(lái)告訴你錯(cuò)誤。有關(guān)更多詳細(xì)信息,請(qǐng)參見(jiàn)手冊(cè)頁(yè)的ERRORS部分。
? ? ? 你可能還想做一件事,即查找未在手冊(cè)頁(yè)本身中指定的結(jié)構(gòu)的定義。例如,gettimeofday()的手冊(cè)頁(yè)有以下概要:
? SYNOPSIS
? #include <sys/time.h>
? int gettimeofday(struct timeval *restrict tp,
? void *restrict tzp);
? ? ? 在這個(gè)頁(yè)面中,你可以看到時(shí)間被放入timeval類型的結(jié)構(gòu)中,但是手冊(cè)頁(yè)可能不會(huì)告訴你這個(gè)結(jié)構(gòu)有哪些字段?。ㄔ谶@個(gè)例子中,它包含在內(nèi),但你可能并非總是如此幸運(yùn))因此,你可能不得不尋找它。所有包含文件都位于/usr/include目錄下,因此你可以用grep這樣的工具來(lái)查找它。例如,你可以鍵入:
? prompt> grep ’struct timeval’ /usr/include/sys/*.h
? ? ? 這讓你在/usr/include/sys中以.h結(jié)尾的所有文件中查找該結(jié)構(gòu)的定義。遺憾的是,這可能不一定有效,因?yàn)榘募赡馨ㄔ趧e處的其他文件小編是一個(gè)有著5年開(kāi)發(fā)經(jīng)驗(yàn)的C/C++程序員,關(guān)于C/C++,自己有做材料的整合,一個(gè)完整的學(xué)習(xí)C/C++的路線,學(xué)習(xí)材料和工具給大家!企鵝<C語(yǔ)言C++編程學(xué)習(xí)>!希望你也能憑自己的努力,成為下一個(gè)優(yōu)秀的程序員。
? ? ? 更好的方法是使用你可以使用的工具,即編譯器。編寫(xiě)一個(gè)包含頭文件time.h的程序,假設(shè)名為main.c。然后,使用編譯器調(diào)用預(yù)處理器,而不是編譯它。預(yù)處理器處理文件中的所有指令,例如#define指令和#include指令。為此,請(qǐng)鍵入gcc -E main.c。結(jié)果是一個(gè)C文件,其中包含所有需要的結(jié)構(gòu)和原型,包括timeval結(jié)構(gòu)的定義。
? ? ? 可能還有找到這些東西的更好方法:google。你應(yīng)該總是google那些你不了解的東西——只要通過(guò)查找就可以學(xué)到很多東西,這令人驚奇!
? ? ? info頁(yè)面
? ? ? info頁(yè)面在尋找文檔方面也非常有用,它為許多GNU工具提供了更詳細(xì)的文檔。你可以通過(guò)運(yùn)行info程序或通過(guò)emacs(黑客的首選編輯器)執(zhí)行Meta-x info來(lái)訪問(wèn)info頁(yè)面。像gcc這樣的程序有數(shù)百個(gè)標(biāo)志,其中一些標(biāo)志非常有用。gmake還有許多功能可以改善你的構(gòu)建環(huán)境。最后,gdb是一個(gè)非常復(fù)雜的調(diào)試器。閱讀man和info頁(yè)面,嘗試以前沒(méi)有嘗試過(guò)的功能,成為編程工具的強(qiáng)大用!
全球最大的C/C++、編程愛(ài)好者的聚集地就在我這里,<C語(yǔ)言C++編程學(xué)習(xí)>!歡迎初學(xué)和進(jìn)階中的小伙伴。希望你也能憑自己的努力,成為下一個(gè)優(yōu)秀的程序員。工作需要、感興趣、為了入行、轉(zhuǎn)行需要學(xué)習(xí)C/C++的伙伴可以跟我一起學(xué)習(xí)!”
關(guān)注我和我的專欄,帶你遨游代碼世界!C語(yǔ)言/C++進(jìn)階之路 - 專題 - 簡(jiǎn)書(shū)