程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 如何運用gcc編譯器

如何運用gcc編譯器

編輯:關於C++

如何運用gcc編譯器。本站提示廣大學習愛好者:(如何運用gcc編譯器)文章只能為提供參考,不一定能成為您想要的結果。以下是如何運用gcc編譯器正文


  要想讀懂本文,你需求對C言語有根本的理解,本文將引見如何運用gcc編譯器。 首先,我們引見如何在命令行的方式下運用編譯器編譯復雜的C源代碼。 然後,我們扼要引見一下編譯器終究作了哪些任務,以及如何控制編譯的進程。 我們也扼要引見了調試器的運用辦法。

gcc引見

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

1、開端

  首先,我們應該知道如何調用編譯器。實踐上,這很復雜。我們將從那個著名的第一個C順序開端。

#include <stdio.h>
int main(int argc,char **argv)
{
  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

假如沒有錯誤,就沒有問題。假如編譯不能經過,那麼就修正它直到能經過為止。

2、預編譯

  編譯器是怎樣知道 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 選項,而是讓它把輸入傳送給編譯器。

3、編譯

  作為一個兩頭步驟,gcc把你的代碼翻譯成匯編言語。它一定要這樣做,它必需經過剖析你的代碼搞清楚你終究想要做什麼。假如你犯了語法錯誤,它就會通知你,這樣編譯就失敗了。 人們有時會把這一步曲解為整個進程。但是,實踐上還有許多任務要gcc去做呢。

4、匯編

  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'函數在哪裡。 那麼,究竟什麼是銜接?我們怎樣通知編譯器到哪裡尋覓這個函數呢?

5、銜接

銜接器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)中的目的代碼銜接出去。這種方式使我們可以運用已能正確任務的其別人的函數,只關懷我們所要處理的問題。 這就是為什麼頭文件中普通只含無數據和函數聲明,而沒有函數體。普通,你可以為銜接器創立目的文件或函數庫,以便銜接進可執行文件。我們的代碼能夠發生問題,由於在頭文件中我們沒有放入任何函數聲明。為了確保一切順利,我們還能做什麼呢?

6、另外兩個重要選項

-Wall 選項可以翻開一切類型的語法正告,以便協助我們確定代碼是正確的, 並且盡能夠完成可移植性。當我們運用這個選項編譯我們的代碼時,我們將看到下述正告:

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

  這讓我們知道還有一些任務要做。我們需求在頭文件中參加一行代碼,以便通知編譯器有關 shuffle 函數的一切,讓它可以做必要的反省。聽起來象是一種狡賴,但這樣做可以把函數的定義與完成別離開來,使我們能在任何中央運用我們的函數,只需包括新的頭文件並把它銜接到我們的目的文件中就可以了。上面我們就把這一行參加deck.h中。

void shuffle(deck_t *pdeck);

這就可以消弭那個正告信息了。

  另一個常用編譯器選項是優化選項 -O# (即 -O2)。 這是通知編譯器你需求什麼級別的優化。編譯用具有一整套技巧可以使你的代碼運轉得更快一點。 關於象我們這種小順序,你能夠留意不到差異,但關於大型順序來說,它可以大幅度進步運轉速度。 你會常常碰到它,所以你應該知道它的意思。

7、調試

  我們都知道,代碼經過了編譯並不意味著它按我們得要求任務了。你可以運用上面的命令驗證能否一切的號碼都被運用了

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 語句好多了,是不是?

8、小結

  本文大體引見了編譯和調試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。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved