日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

gcc編譯器使用入門--溪流百里涼

 jollyme 2006-02-24


 
摘要:

要想讀懂本文,你需要對C語言有基本的了解,本文將介紹如何使用gcc編譯器。首先,我們介紹如何在命令行方式下使用編譯器編譯簡單的C源代碼。然后,我們簡要介紹一下編譯器究竟作了那些工作,以及如何控制編譯過程。我們也簡要介紹了調(diào)試器的使用方法。

GCC rules
你能想象使用封閉源代碼的私有編譯器編譯自由軟件嗎?你怎么知道編譯器在你的可執(zhí)行文件中加入了什么?可能會加入各種后門和木馬。Ken Thompson是一個著名的黑客,他編寫了一個編譯器,當(dāng)編譯器編譯自己時(shí),就在‘login‘程序中留下后門和永久的木馬。請到 這里 閱讀他對這個杰作的描述。幸運(yùn)的是,我們有了gcc。當(dāng)你進(jìn)行 configure; make; make install 時(shí), gcc在幕后做了很多繁重的工作。如何才能讓gcc為我們工作呢?我們將開始編寫一個紙牌游戲,不過我們只是為了演示編譯器的功能,所以盡可能地精簡了代碼。我們將從頭開始一步一步地做,以便理解編譯過程,了解為了制作可執(zhí)行文件需要做些什么,按什么順序做。我們將看看如何編譯C程序,以及如何使用編譯選項(xiàng)讓gcc按照我們的要求工作。步驟(以及所用工具)如下: 預(yù)編譯 (gcc -E), 編譯 (gcc), 匯編 (as),和 連接 (ld)。

 
開始...
首先,我們應(yīng)該知道如何調(diào)用編譯器。實(shí)際上,這很簡單。我們將從那個著名的第一個C程序開始。(各位老前輩,請?jiān)徫遥?


#include <stdio.h>

int main()

{
  printf("Hello World!\n");
}


把這個文件保存為 game.c。 你可以在命令行下編譯它:

gcc game.c

在默認(rèn)情況下,C編譯器將生成一個名為 a.out 的可執(zhí)行文件。你可以鍵入如下命令運(yùn)行它:
a.out

Hello World


每一次編譯程序時(shí),新的 a.out 將覆蓋原來的程序。你無法知道是哪個程序創(chuàng)建了 a.out。我們可以通過使用 -o 編譯選項(xiàng),告訴 gcc我們想把可執(zhí)行文件叫什么名字。我們將把這個程序叫做 game,我們可以使用任何名字,因?yàn)镃沒有Java那樣的命名限制。
gcc -o game game.c


game
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 DECK_H

#define DECKSIZE 52

typedef struct deck_t
{
  int card[DECKSIZE];
  /* number of cards used */
  int dealt;
}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


如果沒有錯誤,就沒有問題。如果編譯不能通過,那么就修改它直到能通過為止。

 
預(yù)編譯

編譯器是怎么知道 deck_t 類型是什么的呢?因?yàn)樵陬A(yù)編譯期間,它實(shí)際上把"deck.h"文件復(fù)制到了"game.c"文件中。源代碼中的預(yù)編譯指示以"#"為前綴。你可以通過在gcc后加上 -E 選項(xiàng)來調(diào)用預(yù)編譯器。


gcc -E -o game_precompile.txt game.c
wc -l game_precompile.txt
  3199 game_precompile.txt

幾乎有3200行的輸出!其中大多數(shù)來自 stdio.h 包含文件,但是如果你查看這個文件的話,我們的聲明也在那里。如果你不用 -o 選項(xiàng)指定輸出文件名的話,它就輸出到控制臺。預(yù)編譯過程通過完成三個主要任務(wù)給了代碼很大的靈活性。
把"include"的文件拷貝到要編譯的源文件中。
用實(shí)際值替代"define"的文本。
在調(diào)用宏的地方進(jìn)行宏替換。
這就使你能夠在整個源文件中使用符號常量(即用DECKSIZE表示一付牌中的紙牌數(shù)量),而符號常量是在一個地方定義的,如果它的值發(fā)生了變化,所有使用符號常量的地方都能自動更新。在實(shí)踐中,你幾乎不需要單獨(dú)使用 -E 選項(xiàng),而是讓它把輸出傳送給編譯器。

 
編譯

作為一個中間步驟,gcc把你的代碼翻譯成匯編語言。它一定要這樣做,它必須通過分析你的代碼搞清楚你究竟想要做什么。如果你犯了語法錯誤,它就會告訴你,這樣編譯就失敗了。人們有時(shí)會把這一步誤解為整個過程。但是,實(shí)際上還有許多工作要gcc去做呢。

 
匯編
as 把匯編語言代碼轉(zhuǎn)換為目標(biāo)代碼。事實(shí)上目標(biāo)代碼并不能在CPU上運(yùn)行,但它離完成已經(jīng)很近了。編譯器選項(xiàng) -c 把 .c 文件轉(zhuǎn)換為以 .o 為擴(kuò)展名的目標(biāo)文件。 如果我們運(yùn)行

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>
#include <stdio.h>
#include <time.h>
#include "deck.h"

static time_t seed = 0;

void shuffle(deck_t *pdeck)
{
  /* Keeps track of what numbers have been used */
  int drawn[DECKSIZE] = {0};
  int i;

  /* One time initialization of rand */
  if(0 == seed)
  {
    seed = time(NULL);
    srand(seed);
  }
  for(i = 0; i < DECKSIZE; i++)
  {
    int value = -1;
    do
    {
      value = rand() % DECKSIZE;
    }
    while(drawn[value] != 0);

    /* mark value as used */
    drawn[value] = 1;

    /* debug statement */
    printf("%i\n", value);
    pdeck->card[i] = value;
  }
  pdeck->dealt = 0;
  return;
}

把這個文件保存為 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)試器。

請注意兩個問題。
我們用傳址方式傳遞參數(shù),你可以從‘&‘(取地址)操作符看出來。這把變量的機(jī)器地址傳遞給了函數(shù),因此函數(shù)自己就能改變變量的值。也可以使用全局變量編寫程序,但是應(yīng)該盡量少使用全局變量。指針是C的一個重要組成部分,你應(yīng)該充分地理解它。
我們在一個新的 .c 文件中使用函數(shù)調(diào)用。操作系統(tǒng)總是尋找名為‘main‘的函數(shù),并從那里開始執(zhí)行。 shuffle.c 中沒有‘main‘函數(shù),因此不能編譯為獨(dú)立的可執(zhí)行文件。我們必須把它與另一個具有‘main‘函數(shù)并調(diào)用‘shuffle‘的程序組合起來。
運(yùn)行命令


gcc -c shuffle.c

并確定它創(chuàng)建了一個名為 shuffle.o 的新文件。編輯game.c文件,在第7行,在 deck_t類型的變量 deck 聲明之后,加上下面這一行:
shuffle(&deck);

現(xiàn)在,如果我們還象以前一樣創(chuàng)建可執(zhí)行文件,我們就會得到一個錯誤
gcc -o game game.c

/tmp/ccmiHnJX.o: In function `main‘:
/tmp/ccmiHnJX.o(.text+0xf): undefined reference to `shuffle‘
collect2: ld returned 1 exit status

編譯成功了,因?yàn)槲覀兊恼Z法是正確的。但是連接步驟卻失敗了,因?yàn)槲覀儧]有告訴編譯器‘shuffle‘函數(shù)在哪里。那么,到底什么是連接?我們怎樣告訴編譯器到哪里尋找這個函數(shù)呢?

 
連接
連接器ld,使用下面的命令,接受前面由 as 創(chuàng)建的目標(biāo)文件并把它轉(zhuǎn)換為可執(zhí)行文件


gcc -o game game.o shuffle.o

這將把兩個目標(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ù)聲明。為了確保一切順利,我們還能做什么呢?

 
另外兩個重要選項(xiàng)
-Wall 選項(xiàng)可以打開所有類型的語法警告,以便幫助我們確定代碼是正確的,并且盡可能實(shí)現(xiàn)可移植性。當(dāng)我們使用這個選項(xiàng)編譯我們的代碼時(shí),我們將看到下述警告:

game.c:9: warning: implicit declaration of function `shuffle‘

這讓我們知道還有一些工作要做。我們需要在頭文件中加入一行代碼,以便告訴編譯器有關(guān) shuffle 函數(shù)的一切,讓它可以做必要的檢查。聽起來象是一種狡辯,但這樣做 可以把函數(shù)的定義與實(shí)現(xiàn)分離開來,使我們能在任何地方使用我們的函數(shù),只要包含新的頭文件 并把它連接到我們的目標(biāo)文件中就可以了。下面我們就把這一行加入deck.h中。
void shuffle(deck_t *pdeck);

這就可以消除那個警告信息了。

另一個常用編譯器選項(xiàng)是優(yōu)化選項(xiàng) -O# (即 -O2)。 這是告訴編譯器你需要什么級別的優(yōu)化。編譯器具有一整套技巧可以使你的代碼運(yùn)行得更快一點(diǎn)。對于象我們這種小程序,你可能注意不到差別,但對于大型程序來說,它可以大幅度提高運(yùn)行速度。你會經(jīng)常碰到它,所以你應(yīng)該知道它的意思。

 
調(diào)試

我們都知道,代碼通過了編譯并不意味著它按我們得要求工作了。你可以使用下面的命令驗(yàn)證是否所有的號碼都被使用了

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),所以命令行成了:
gcc -g -c shuffle.c game.c
gcc -g -o game game.o shuffle.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 語句好多了,是不是?

 
小結(jié)
本文大體介紹了編譯和調(diào)試C程序的方法。我們討論了編譯器走過的步驟,以及為了讓編譯器做這些工作應(yīng)該給gcc傳遞哪些選項(xiàng)。我們簡述了有關(guān)連接共享函數(shù)庫的問題,最后介紹了調(diào)試器。真正了解你所從事的工作還需要付出許多努力,但我希望本文能讓你正確地起步。你可以在 gcc、 as 和 ld的 man 和 info page中找到更多的信息。

自己編寫代碼可以讓你學(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)。
一個給指定玩家發(fā)一定數(shù)量牌的函數(shù)。記住在紙牌中要增加“已發(fā)牌”的數(shù)量,以便能知道還有那些牌可發(fā)。還要記住玩家手中還有多少牌。
一些與用戶的交互,問問玩家是否還要另一張牌。
一個能打印玩家手中的牌的函數(shù)。 card 等于value % 13 (得數(shù)為0到12),suit 等于 value / 13 (得數(shù)為0到3)。
一個能確定玩家手中的value的函數(shù)。Ace的value為零并且可以等于1或11。King的value為12并且可以等于10。


 

 

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多