要想讀懂本文,你需要對C語言有基本的了解,本文將介紹如何使用gcc編譯器。首先,我們介紹如何在命令行方式下使用編譯器編譯簡單的C源代碼。然后,我們簡要介紹一下編譯器究竟作了那些工作,以及如何控制編譯過程。我們也簡要介紹了調(diào)試器的使用方法。
GCC rules
int main() {
gcc game.c 在默認(rèn)情況下,C編譯器將生成一個名為 a.out 的可執(zhí)行文件。你可以鍵入如下命令運(yùn)行它: Hello World
到現(xiàn)在為止,我們離一個有用的程序還差得很遠(yuǎn)。如果你覺得沮喪,你可以想一想我們已經(jīng)編譯并運(yùn)行了一個程序。因?yàn)槲覀儗⒁稽c(diǎn)一點(diǎn)為這個程序添加功能,所以我們必須保證讓它能夠運(yùn)行。似乎每個剛開始學(xué)編程的程序員都想一下子編一個1000行的程序,然后一次修改所有的錯誤。沒有人,我是說沒有人,能做到這個。你應(yīng)該先編一個可以運(yùn)行的小程序,修改它,然后再次讓它運(yùn)行。這可以限制你一次修改的錯誤數(shù)量。另外,你知道剛才做了哪些修改使程序無法運(yùn)行,因此你知道應(yīng)該把注意力放在哪里。這可以防止這樣的情況出現(xiàn):你認(rèn)為你編寫的東西應(yīng)該能夠工作,它也能通過編譯,但它就是不能運(yùn)行。請切記,能夠通過編譯的程序并不意味著它是正確的。 下一步為我們的游戲編寫一個頭文件。頭文件把數(shù)據(jù)類型和函數(shù)聲明集中到了一處。這可以保證數(shù)據(jù)結(jié)構(gòu)定義的一致性,以便程序的每一部分都能以同樣的方式看待一切事情。 #ifndef DECK_H #define DECKSIZE 52 typedef struct deck_t #endif /* DECK_H */ 把這個文件保存為 deck.h。只能編譯 .c 文件,所以我們必須修改 game.c。在game.c的第2行,寫上 #include "deck.h"。在第5行寫上 deck_t deck;。為了保證我們沒有搞錯,把它重新編譯一次。 gcc -o game game.c
幾乎有3200行的輸出!其中大多數(shù)來自 stdio.h 包含文件,但是如果你查看這個文件的話,我們的聲明也在那里。如果你不用 -o 選項(xiàng)指定輸出文件名的話,它就輸出到控制臺。預(yù)編譯過程通過完成三個主要任務(wù)給了代碼很大的靈活性。 gcc -c game.c 我們就自動創(chuàng)建了一個名為game.o的文件。這里我們碰到了一個重要的問題。我們可以用任意一個 .c 文件創(chuàng)建一個目標(biāo)文件。正如我們在下面所看到的,在連接步驟中我們可以把這些目標(biāo)文件組合成可執(zhí)行文件。讓我們繼續(xù)介紹我們的例子。因?yàn)槲覀冋诰帉懸粋€紙牌游戲,我們已經(jīng)把一付牌定義為 deck_t,我們將編寫一個洗牌函數(shù)。這個函數(shù)接受一個指向deck類型的指針,并把一付隨機(jī)的牌裝入deck類型。它使用‘drawn‘ 數(shù)組跟蹤記錄那些牌已經(jīng)用過了。這個具有DECKSIZE個元素的數(shù)組可以防止我們重復(fù)使用一張牌。 #include <stdlib.h> static time_t seed = 0; void shuffle(deck_t *pdeck) /* One time initialization of rand */ /* mark value as used */ /* debug statement */ 把這個文件保存為 shuffle.c。我們在這個代碼中加入了一條調(diào)試語句,以便運(yùn)行時(shí),能輸出所產(chǎn)生的牌號。這并沒有為我們的程序添加功能,但是現(xiàn)在到了關(guān)鍵時(shí)刻,我們看看究竟發(fā)生了什么。因?yàn)槲覀兊挠螒蜻€在初級階段,我們沒有別的辦法確定我們的函數(shù)是否實(shí)現(xiàn)了我們要求的功能。使用那條printf語句,我們就能準(zhǔn)確地知道現(xiàn)在究竟發(fā)生了什么,以便在開始下一階段之前我們知道牌已經(jīng)洗好了。在我們對它的工作感到滿意之后,我們可以把那一行語句從代碼中刪掉。這種調(diào)試程序的技術(shù)看起來很粗糙,但它使用最少的語句完成了調(diào)試任務(wù)。以后我們再介紹更復(fù)雜的調(diào)試器。 請注意兩個問題。
并確定它創(chuàng)建了一個名為 shuffle.o 的新文件。編輯game.c文件,在第7行,在 deck_t類型的變量 deck 聲明之后,加上下面這一行: 現(xiàn)在,如果我們還象以前一樣創(chuàng)建可執(zhí)行文件,我們就會得到一個錯誤 /tmp/ccmiHnJX.o: In function `main‘: 編譯成功了,因?yàn)槲覀兊恼Z法是正確的。但是連接步驟卻失敗了,因?yàn)槲覀儧]有告訴編譯器‘shuffle‘函數(shù)在哪里。那么,到底什么是連接?我們怎樣告訴編譯器到哪里尋找這個函數(shù)呢?
這將把兩個目標(biāo)文件組合起來并創(chuàng)建可執(zhí)行文件 game。 連接器從shuffle.o目標(biāo)文件中找到 shuffle 函數(shù),并把它包括進(jìn)可執(zhí)行文件。目標(biāo)文件的真正好處在于,如果我們想再次使用那個函數(shù),我們所要做的就是包含"deck.h" 文件并把 shuffle.o 目標(biāo)文件連接到新的可執(zhí)行文件中。 象這樣的代碼重用是經(jīng)常發(fā)生的。雖然我們并沒有編寫前面作為調(diào)試語句調(diào)用的 printf 函數(shù),連接器卻能從我們用 #include <stdlib.h> 語句包含的文件中找到它的聲明,并把存儲在C庫(/lib/libc.so.6)中的目標(biāo)代碼連接進(jìn)來。這種方式使我們可以使用已能正確工作的其他人的函數(shù),只關(guān)心我們所要解決的問題。這就是為什么頭文件中一般只含有數(shù)據(jù)和函數(shù)聲明,而沒有函數(shù)體。一般,你可以為連接器創(chuàng)建目標(biāo)文件或函數(shù)庫,以便連接進(jìn)可執(zhí)行文件。我們的代碼可能產(chǎn)生問題,因?yàn)樵陬^文件中我們沒有放入任何函數(shù)聲明。為了確保一切順利,我們還能做什么呢? game.c:9: warning: implicit declaration of function `shuffle‘ 這讓我們知道還有一些工作要做。我們需要在頭文件中加入一行代碼,以便告訴編譯器有關(guān) shuffle 函數(shù)的一切,讓它可以做必要的檢查。聽起來象是一種狡辯,但這樣做 可以把函數(shù)的定義與實(shí)現(xiàn)分離開來,使我們能在任何地方使用我們的函數(shù),只要包含新的頭文件 并把它連接到我們的目標(biāo)文件中就可以了。下面我們就把這一行加入deck.h中。 這就可以消除那個警告信息了。 另一個常用編譯器選項(xiàng)是優(yōu)化選項(xiàng) -O# (即 -O2)。 這是告訴編譯器你需要什么級別的優(yōu)化。編譯器具有一整套技巧可以使你的代碼運(yùn)行得更快一點(diǎn)。對于象我們這種小程序,你可能注意不到差別,但對于大型程序來說,它可以大幅度提高運(yùn)行速度。你會經(jīng)常碰到它,所以你應(yīng)該知道它的意思。 game | sort - n | less 并且檢查有沒有遺漏。如果有問題我們該怎么辦?我們?nèi)绾尾拍苌钊氲讓硬檎义e誤呢? 你可以使用調(diào)試器檢查你的代碼。大多數(shù)發(fā)行版都提供著名的調(diào)試器:gdb。如果那些眾多的命令行選項(xiàng)讓你感到無所適從,那么你可以使用KDE提供的一個很好的前端工具 KDbg。還有一些其它的前端工具,它們都很相似。要開始調(diào)試,你可以選擇 File->Executable 然后找到你的 game 程序。當(dāng)你按下F5鍵或選擇 Execution->從菜單運(yùn)行時(shí),你可以在另一個窗口中看到輸出。怎么回事?在那個窗口中我們什么也看不到。不要擔(dān)心,KDbg沒有出問題。問題在于我們在可執(zhí)行文件中沒有加入任何調(diào)試信息,所以KDbg不能告訴我們內(nèi)部發(fā)生了什么。編譯器選項(xiàng) -g 可以把必要的調(diào)試信息加入目標(biāo)文件。你必須用這個選項(xiàng)編譯目標(biāo)文件(擴(kuò)展名為.o),所以命令行成了: 這就把鉤子放入了可執(zhí)行文件,使gdb和KDbg能指出運(yùn)行情況。調(diào)試是一種很重要的技術(shù),很值得你花時(shí)間學(xué)習(xí)如何使用。調(diào)試器幫助程序員的方法是它能在源代碼中設(shè)置“斷點(diǎn)”?,F(xiàn)在你可以用右鍵單擊調(diào)用 shuffle 函數(shù)的那行代碼,試著設(shè)置斷點(diǎn)。那一行邊上會出現(xiàn)一個紅色的小圓圈?,F(xiàn)在當(dāng)你按下F5鍵時(shí),程序就會在那一行停止執(zhí)行。按F8可以跳入shuffle函數(shù)。呵,我們現(xiàn)在可以看到 shuffle.c 中的代碼了!我們可以控制程序一步一步地執(zhí)行,并看到究竟發(fā)生了什么事。如果你把光標(biāo)暫停在局部變量上,你將能看到變量的內(nèi)容。太好了。這比那條 printf 語句好多了,是不是? 自己編寫代碼可以讓你學(xué)到更多的東西。作為練習(xí)你可以以本文的紙牌游戲?yàn)榛A(chǔ),編寫一個21點(diǎn)游戲。那時(shí)你可以學(xué)學(xué)如何使用調(diào)試器。使用GUI的KDbg開始可以更容易一些。如果你每次只加入一點(diǎn)點(diǎn)功能,那么很快就能完成。切記,一定要保持程序一直能運(yùn)行! 要想編寫一個完整的游戲,你需要下面這些內(nèi)容: 一個紙牌玩家的定義(即,你可以把deck_t定義為player_t)。
|
|