青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆 - 298  文章 - 377  trackbacks - 0
<2008年12月>
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910

常用鏈接

留言簿(34)

隨筆分類

隨筆檔案

文章檔案

相冊

收藏夾

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

GCC - 一切從這里開始

 

摘要:

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

 

GCC rules

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

 

開始...

首先,我們應該知道如何調用編譯器。實際上,這很簡單。我們將從那個著名的第一個C程序開始。(各位老前輩,請原諒我)。

#include <stdio.h>

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

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

gcc game.c

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

a.out
Hello World

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

gcc -o game game.c
game
Hello World

到現在為止,我們離一個有用的程序還差得很遠。如果你覺得沮喪,你可以想一想我們已經編譯并運行了一個程序。因為我們將一點一點為這個程序添加功能,所以我們必須保證讓它能夠運行。似乎每個剛開始學編程的程序員都想一下子編一個1000行的程序,然后一次修改所有的錯誤。沒有人,我是說沒有人,能做到這個。你應該先編一個可以運行的小程序,修改它,然后再次讓它運行。這可以限制你一次修改的錯誤數量。另外,你知道剛才做了哪些修改使程序無法運行,因此你知道應該把注意力放在哪里。這可以防止這樣的情況出現:你認為你編寫的東西應該能夠工作,它也能通過編譯,但它就是不能運行。請切記,能夠通過編譯的程序并不意味著它是正確的。

下一步為我們的游戲編寫一個頭文件。頭文件把數據類型和函數聲明集中到了一處。這可以保證數據結構定義的一致性,以便程序的每一部分都能以同樣的方式看待一切事情。

#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

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

 

預編譯

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

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

幾乎有3200行的輸出!其中大多數來自 stdio.h 包含文件,但是如果你查看這個文件的話,我們的聲明也在那里。如果你不用 -o 選項指定輸出文件名的話,它就輸出到控制臺。預編譯過程通過完成三個主要任務給了代碼很大的靈活性。

  1. 把"include"的文件拷貝到要編譯的源文件中。
  2. 用實際值替代"define"的文本。
  3. 在調用宏的地方進行宏替換。

這就使你能夠在整個源文件中使用符號常量(即用DECKSIZE表示一付牌中的紙牌數量),而符號常量是在一個地方定義的,如果它的值發生了變化,所有使用符號常量的地方都能自動更新。在實踐中,你幾乎不需要單獨使用 -E 選項,而是讓它把輸出傳送給編譯器。

 

編譯

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

 

匯編

as 把匯編語言代碼轉換為目標代碼。事實上目標代碼并不能在CPU上運行,但它離完成已經很近了。編譯器選項 -c 把 .c 文件轉換為以 .o 為擴展名的目標文件。 如果我們運行

gcc -c game.c

我們就自動創建了一個名為game.o的文件。這里我們碰到了一個重要的問題。我們可以用任意一個 .c 文件創建一個目標文件。正如我們在下面所看到的,在連接步驟中我們可以把這些目標文件組合成可執行文件。讓我們繼續介紹我們的例子。因為我們正在編寫一個紙牌游戲,我們已經把一付牌定義為 deck_t,我們將編寫一個洗牌函數。這個函數接受一個指向deck類型的指針,并把一付隨機的牌裝入deck類型。它使用'drawn' 數組跟蹤記錄那些牌已經用過了。這個具有DECKSIZE個元素的數組可以防止我們重復使用一張牌。

#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。我們在這個代碼中加入了一條調試語句,以便運行時,能輸出所產生的牌號。這并沒有為我們的程序添加功能,但是現在到了關鍵時刻,我們看看究竟發生了什么。因為我們的游戲還在初級階段,我們沒有別的辦法確定我們的函數是否實現了我們要求的功能。使用那條printf語句,我們就能準確地知道現在究竟發生了什么,以便在開始下一階段之前我們知道牌已經洗好了。在我們對它的工作感到滿意之后,我們可以把那一行語句從代碼中刪掉。這種調試程序的技術看起來很粗糙,但它使用最少的語句完成了調試任務。以后我們再介紹更復雜的調試器。

請注意兩個問題。

  1. 我們用傳址方式傳遞參數,你可以從'&'(取地址)操作符看出來。這把變量的機器地址傳遞給了函數,因此函數自己就能改變變量的值。也可以使用全局變量編寫程序,但是應該盡量少使用全局變量。指針是C的一個重要組成部分,你應該充分地理解它。
  2. 我們在一個新的 .c 文件中使用函數調用。操作系統總是尋找名為'main'的函數,并從那里開始執行。 shuffle.c 中沒有'main'函數,因此不能編譯為獨立的可執行文件。我們必須把它與另一個具有'main'函數并調用'shuffle'的程序組合起來。

運行命令

gcc -c shuffle.c

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

shuffle(&deck);

現在,如果我們還象以前一樣創建可執行文件,我們就會得到一個錯誤

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

編譯成功了,因為我們的語法是正確的。但是連接步驟卻失敗了,因為我們沒有告訴編譯器'shuffle'函數在哪里。那么,到底什么是連接?我們怎樣告訴編譯器到哪里尋找這個函數呢?

 

連接

連接器ld,使用下面的命令,接受前面由 as 創建的目標文件并把它轉換為可執行文件

gcc -o game game.o shuffle.o

這將把兩個目標文件組合起來并創建可執行文件 game

連接器從shuffle.o目標文件中找到 shuffle 函數,并把它包括進可執行文件。目標文件的真正好處在于,如果我們想再次使用那個函數,我們所要做的就是包含"deck.h" 文件并把 shuffle.o 目標文件連接到新的可執行文件中。

象這樣的代碼重用是經常發生的。雖然我們并沒有編寫前面作為調試語句調用的 printf 函數,連接器卻能從我們用 #include <stdlib.h> 語句包含的文件中找到它的聲明,并把存儲在C庫(/lib/libc.so.6)中的目標代碼連接進來。這種方式使我們可以使用已能正確工作的其他人的函數,只關心我們所要解決的問題。這就是為什么頭文件中一般只含有數據和函數聲明,而沒有函數體。一般,你可以為連接器創建目標文件或函數庫,以便連接進可執行文件。我們的代碼可能產生問題,因為在頭文件中我們沒有放入任何函數聲明。為了確保一切順利,我們還能做什么呢?

 

另外兩個重要選項

-Wall 選項可以打開所有類型的語法警告,以便幫助我們確定代碼是正確的,并且盡可能實現可移植性。當我們使用這個選項編譯我們的代碼時,我們將看到下述警告:

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

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

void shuffle(deck_t *pdeck);

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

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

 

調試

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

game | sort - n | less

并且檢查有沒有遺漏。如果有問題我們該怎么辦?我們如何才能深入底層查找錯誤呢?

你可以使用調試器檢查你的代碼。大多數發行版都提供著名的調試器:gdb。如果那些眾多的命令行選項讓你感到無所適從,那么你可以使用KDE提供的一個很好的前端工具 KDbg。還有一些其它的前端工具,它們都很相似。要開始調試,你可以選擇 File->Executable 然后找到你的 game 程序。當你按下F5鍵或選擇 Execution->從菜單運行時,你可以在另一個窗口中看到輸出。怎么回事?在那個窗口中我們什么也看不到。不要擔心,KDbg沒有出問題。問題在于我們在可執行文件中沒有加入任何調試信息,所以KDbg不能告訴我們內部發生了什么。編譯器選項 -g 可以把必要的調試信息加入目標文件。你必須用這個選項編譯目標文件(擴展名為.o),所以命令行成了:

gcc -g -c shuffle.c game.c
gcc -g -o game game.o shuffle.o

這就把鉤子放入了可執行文件,使gdb和KDbg能指出運行情況。調試是一種很重要的技術,很值得你花時間學習如何使用。調試器幫助程序員的方法是它能在源代碼中設置“斷點”。現在你可以用右鍵單擊調用 shuffle 函數的那行代碼,試著設置斷點。那一行邊上會出現一個紅色的小圓圈。現在當你按下F5鍵時,程序就會在那一行停止執行。按F8可以跳入shuffle函數。呵,我們現在可以看到 shuffle.c 中的代碼了!我們可以控制程序一步一步地執行,并看到究竟發生了什么事。如果你把光標暫停在局部變量上,你將能看到變量的內容。太好了。這比那條 printf 語句好多了,是不是?

 

小結

本文大體介紹了編譯和調試C程序的方法。我們討論了編譯器走過的步驟,以及為了讓編譯器做這些工作應該給gcc傳遞哪些選項。我們簡述了有關連接共享函數庫的問題,最后介紹了調試器。真正了解你所從事的工作還需要付出許多努力,但我希望本文能讓你正確地起步。你可以在 gccasldmaninfo page中找到更多的信息。

自己編寫代碼可以讓你學到更多的東西。作為練習你可以以本文的紙牌游戲為基礎,編寫一個21點游戲。那時你可以學學如何使用調試器。使用GUI的KDbg開始可以更容易一些。如果你每次只加入一點點功能,那么很快就能完成。切記,一定要保持程序一直能運行!

要想編寫一個完整的游戲,你需要下面這些內容:

  • 一個紙牌玩家的定義(即,你可以把deck_t定義為player_t)。
  • 一個給指定玩家發一定數量牌的函數。記住在紙牌中要增加“已發牌”的數量,以便能知道還有那些牌可發。還要記住玩家手中還有多少牌。
  • 一些與用戶的交互,問問玩家是否還要另一張牌。
  • 一個能打印玩家手中的牌的函數。 card 等于value % 13 (得數為0到12),suit 等于 value / 13 (得數為0到3)。
  • 一個能確定玩家手中的value的函數。Ace的value為零并且可以等于1或11。King的value為12并且可以等于10。

 

站點鏈接

posted on 2007-05-10 00:55 聶文龍 閱讀(168) 評論(0)  編輯 收藏 引用

只有注冊用戶登錄后才能發表評論。
網站導航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            小黄鸭视频精品导航| 影音先锋日韩有码| 亚洲免费婷婷| 欧美一区视频| 久久婷婷蜜乳一本欲蜜臀| 欧美在线日韩| 麻豆精品在线播放| 欧美精品一区二区三区一线天视频 | 欧美亚洲视频一区二区| 午夜精品免费在线| 久久久久九九九| 亚洲国产日本| 亚洲黄色性网站| 中文有码久久| 久久久久高清| 欧美视频中文字幕| 激情六月婷婷久久| 一区二区三区欧美亚洲| 欧美与黑人午夜性猛交久久久| 午夜欧美大片免费观看| 欧美在线观看网站| 老司机精品久久| 亚洲精品自在久久| 新狼窝色av性久久久久久| 美国成人直播| 欧美四级在线| 又紧又大又爽精品一区二区| 亚洲日韩视频| 久久精品国产精品| 一本久久a久久精品亚洲| 久久久噜噜噜久久狠狠50岁| 欧美日韩一卡| 亚洲国产精品一区制服丝袜| 亚洲欧美国产精品桃花| 欧美成人资源网| 亚洲欧美国产日韩中文字幕| 欧美aⅴ99久久黑人专区| 国产日产欧产精品推荐色| 亚洲另类视频| 久久尤物视频| 亚洲欧美大片| 国产精品mv在线观看| 亚洲精品一区二区三区福利| 久久久.com| 亚洲自拍啪啪| 欧美性猛交视频| 一区二区三区国产在线| 亚洲二区视频| 久久综合狠狠综合久久综青草| 国产麻豆精品theporn| 亚洲免费人成在线视频观看| 亚洲国产精品一区二区久| 久久久久久久久岛国免费| 国产视频自拍一区| 欧美一区影院| 亚洲欧美影院| 国产午夜精品美女毛片视频| 亚洲自拍啪啪| 中日韩美女免费视频网站在线观看| 男女视频一区二区| 亚洲人成精品久久久久| 欧美成人tv| 美女精品在线| 亚洲免费高清| 在线一区二区三区四区| 国产精品成人一区二区| 亚洲欧美日韩国产综合精品二区| 一本一本久久a久久精品综合妖精| 欧美日韩直播| 欧美一区二区三区免费视| 亚洲在线中文字幕| 狠狠干综合网| 亚洲国产成人精品久久久国产成人一区| 久久偷窥视频| 99视频日韩| 在线视频精品| 国产一区二区三区在线观看视频 | 99国内精品久久| 欧美黄色影院| 欧美欧美在线| 亚洲欧美日韩国产成人精品影院| 亚洲深夜福利在线| 国产一区二区三区高清在线观看| 久久久综合激的五月天| 蜜臀av国产精品久久久久| 日韩视频免费看| 亚洲午夜精品久久久久久app| 国产精品一区二区你懂得| 久久综合精品国产一区二区三区| 久久久久国色av免费看影院| 日韩午夜三级在线| 午夜欧美理论片| 欧美美女操人视频| 香蕉av777xxx色综合一区| 久久综合999| 一区二区久久久久久| 午夜精品久久久久久久| 亚洲精品视频免费观看| 亚洲性色视频| 亚洲美女色禁图| 亚洲综合精品| 99re国产精品| 亚洲午夜伦理| 91久久久一线二线三线品牌| 亚洲视频在线观看| 亚洲欧洲一区二区在线播放| 在线亚洲精品福利网址导航| 亚洲高清激情| 亚洲一区二区日本| 亚洲精品人人| 久久精品日产第一区二区三区 | 欧美日韩国产经典色站一区二区三区| 性欧美精品高清| 欧美电影在线| 久热成人在线视频| 国产精品无码永久免费888| 亚洲国产成人av| 国产综合自拍| 香蕉尹人综合在线观看| 亚洲视频每日更新| 欧美成人激情视频| 欧美成人高清| 在线观看视频一区| 亚洲欧美经典视频| 亚洲一二三区在线| 欧美精品1区2区| 亚洲第一网站| 亚洲第一在线综合在线| 欧美亚洲专区| 久久激情中文| 国产日韩在线看| 欧美一级久久| 欧美一级黄色网| 欧美影院在线播放| 麻豆久久精品| 亚洲大胆av| 亚洲国产精品一区二区第四页av| 久久国产精品毛片| 久久精品一区二区| 黄色成人在线免费| 久久久久看片| 欧美高清在线视频| 一区免费观看视频| 久久综合久久久| 欧美韩日亚洲| 亚洲久色影视| 欧美午夜免费电影| 亚洲一区二区精品在线观看| 欧美一区二区三区免费视频| 国产欧美日韩精品在线| 久久高清福利视频| 欧美高清影院| av成人免费在线| 国产精品国产三级国产专播精品人 | 一区二区三区我不卡| 免费欧美高清视频| 夜夜嗨av一区二区三区免费区| 亚洲欧美日韩国产成人| 国产日韩精品一区二区三区在线| 欧美一区二区啪啪| 欧美高清视频在线| 午夜国产精品影院在线观看| 国内一区二区三区| 欧美精品成人91久久久久久久| 一区二区三区精品| 免费观看不卡av| 在线一区二区三区四区| 国产日产欧产精品推荐色 | 影音先锋久久| 欧美日韩国产999| 久久国产乱子精品免费女| 亚洲激情第一区| 欧美一区1区三区3区公司| 狠狠干狠狠久久| 欧美日韩一二三四五区| 久久女同精品一区二区| 99国产欧美久久久精品| 老鸭窝91久久精品色噜噜导演| 日韩视频不卡| 国语精品中文字幕| 欧美亚洲成人精品| 欧美r片在线| 亚洲免费在线观看视频| 欧美激情女人20p| 欧美在线电影| 亚洲少妇一区| 亚洲人成人一区二区在线观看| 国产精品视频内| 欧美日韩一区在线| 蜜桃伊人久久| 久久成人av少妇免费| 一区二区三区不卡视频在线观看 | 免费日韩精品中文字幕视频在线| 亚洲一区二区黄| 亚洲精美视频| 欧美一区二区三区在线播放| 亚洲一区二区在线免费观看视频| 影音先锋久久久| 国产在线国偷精品产拍免费yy| 欧美日精品一区视频|