學習交流加
- 個人qq: 1126137994
- 個人微信: liu1126137994
- 學習交流資源分享qq群: 962535112
1、GCC與gcc
-
GCC (GNU Compiler Collection)
- GNU 編譯器集合,包含眾多語言的編譯器,包括C,C++,Java等等
- GCC多用於嵌入式作業系統的編譯,如Linux,VxWorks,Android等等
-
gcc 單指GCC中的C語言編譯器
- gcc 多用於核心開發以及少數應用程式開發
2、gcc的幕後工作
想了解更多更詳細的關於編譯連結深層次內容,請閱讀書籍《CSAPP》第7章與《程式設計師的自我修養》,因為這裡我的學習記錄只記錄結果與常用的幾個編譯方法。
我們先來看一個簡單的程式:
test.c源程式:
#include <stdio.h>
#include "func.h"
int g_global = 0;
int g_test = 1;
int main(int argc, char *argv[])
{
func();
printf("&g_global = %p\n", &g_global);
printf("&g_test = %p\n", &g_test);
printf("&func = %p\n", &func);
printf("&main = %p\n", &main);
return 0;
}
複製程式碼
func.h標頭檔案:
#include <stdio.h>
void func()
{
#ifdef TEST
printf("TEST = %s\n", TEST);
#endif
return;
}
複製程式碼
在Linux下使用gcc進行編譯:
gcc test.c -o test
複製程式碼
然後執行:
./test
複製程式碼
結果如下:
&g_global = 0x804a020
&g_test = 0x804a014
&func = 0x80483c4
&main = 0x80483c9
複製程式碼
很明顯,上述程式很簡單,大一的新生都知道為什麼。但是今天我們不是學習這個程式的,而是想要了解,執行 gcc test.c -o test 這個命令後,是如何一步一步生成可執行檔案test的。
實際上,上述C程式從原始檔到二進位制可執行檔案,有以下四個步驟:
- 預處理 cpp
- C編譯 cc
- 彙編 as
- 連結 ld
大概編譯一個源程式為二進位制檔案的過程如下圖所示:
當然,上面沒有列出連結器,在生成file.o後,還需要將file.o與系統的庫檔案進行連結,生成最終的可執行檔案。
從而,我們就知道了,gcc其實內部包含了前處理器,編譯器,彙編器,連結器這四部分。
這四部分這裡只是來簡單介紹一下(網上一大堆,本文側重點不在此):
- 前處理器:預處理,將源程式的巨集定義與帶‘#’的部分展開
- 編譯器:將預處理後得到的檔案進行第一次編譯得到對應的彙編源程式
- 彙編器:將第二步得到的彙編源程式進行第二次編譯即彙編,得到二進位制檔案(可重定位檔案)
- 連結器:將可重定位檔案與相應的庫進行連結生成最終的可執行檔案
3、實用的gcc選項
本文的重點來了,上述的內容過於簡單,而本節的內容雖然不難,但是並不被大多數人所瞭解,所以是本文的重點學習記錄。
下面將要學習的gcc選項,在工作中具有很強的實用性。
3.1、預處理選項-解決巨集錯誤
gcc -E file.c -o file.i
複製程式碼
實用上述編譯選項 -E 可以得到預處理後的檔案,有時候我們在程式中定義的巨集可能有錯誤,而這種錯誤又很難找,此時如果能得預處理後的檔案,就可以方便定位錯誤。
3.2、-S引數-輔助編寫彙編程式的好方法
寫彙編程式很難,但是如果先寫成C語言,再將這個C語言轉化成組合語言,就會很簡單。gcc編譯工具中,-S選項,可以達到這個目的。比如以下程式: foo.c程式:
#include <stdio.h>
void foo(){
printf("This is foo().\n");
}
複製程式碼
我們使用如下命令進行編譯:
gcc -S -O2 foo.c -o foo.s
複製程式碼
將會生成一個foo.c相同作用的彙編程式foo.s,如下:
.file "foo.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "This is foo().\n"
.text
.p2align 4,,15
.globl foo
.type foo, @function
foo:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $.LC0, 4(%esp)
movl $1, (%esp)
call __printf_chk
leave
ret
.size foo, .-foo
.ident "GCC: (Ubuntu/Linaro 4.4.4-14ubuntu5.1) 4.4.5"
.section .note.GNU-stack,"",@progbits
複製程式碼
使用-S 引數時,我們可以根據需要使用-O優化選項。從foo.s的內容可以看出,"This is foo().\n" 這個字串是放在.rodata段的。看來獲取C程式對應的彙編程式碼,對C語言實現方面的細節,也有所幫助。
3.3、獲取系統標頭檔案路徑
gcc -v file.c
複製程式碼
獲取file.c使用的系統標頭檔案的位置
3.4、產生對映檔案
如果我們想要知道程式中各個符號的記憶體佈局的資訊,可以使用如下命令:
gcc -Wl,-Map=file.map file.c -o file
複製程式碼
3.5、通過選項定義巨集
有時候程式中需要的某一個常量會依賴工作環境的不同而改變,這個時候,我們可以將這個常量定義為巨集,但是這樣,我們還是需要每次都在源程式中將巨集的值改變,這也很麻煩,此時就可以利用編譯選項 -D,在編譯的命令列進行巨集定義。
還有就是程式中或許會存在下屬這樣的程式碼: test.c程式:
#include <stdio.h>
#include "func.h"
int g_global = 0;
int g_test = 1;
int main(int argc, char *argv[])
{
func();
printf("&g_global = %p\n", &g_global);
printf("&g_test = %p\n", &g_test);
printf("&func = %p\n", &func);
printf("&main = %p\n", &main);
return 0;
}
複製程式碼
test.h標頭檔案:
#include <stdio.h>
void func()
{
#ifdef TEST
printf("TEST = %s\n", TEST);
#endif
return;
}
複製程式碼
在標頭檔案中,有一處定義 # ifdef TEST ....
很明顯,上面的兩個檔案,都沒有定義這個TEST,所以程式執行結果如下:
&g_global = 0x804a020
&g_test = 0x804a014
&func = 0x80483c4
&main = 0x80483c9
複製程式碼
但是可能在某個場合,又必須要使用TEST定義,那麼此時,我們肯定不願意在程式中改來改去,此時就利用編譯器的 -D選項,來定義這個TEST。如下編譯命令:
gcc -D'TEST="test" ' test.c -o test
複製程式碼
執行程式後,結果如下:
TEST = test
&g_global = 0x804a020
&g_test = 0x804a014
&func = 0x80483c4
&main = 0x80483e1
複製程式碼
3.6、生成依賴關係
大多數人應該知道make,如果不知道也沒有關係。 在makefile中,make需要通過依賴關係來決定,每次構建時哪些檔案需要重新編譯。使用gcc的-M選項,可以得到make所需要的原始檔的依賴關係。-MM選項可以讓gcc生成不包含系統檔案的依賴關係。
比如有如下原始檔: main.c原始檔(main.h與foo.c的內容是什麼都行)
#include <stdio.h>
#include "main.h"
#include "foo.c"
int main(){
printf("Hello world!\n");
return 0;
}
複製程式碼
對其進行如下編譯
gcc -M main.c
複製程式碼
將得到如下輸出:
可以看到,這句是make所需要的main.c的依賴關係。
如果使用如下命令的話:
gcc -MM main.c
複製程式碼
將得到如下輸出:
結果顯而易見!!!3.7、指定連結庫
當一個可執行程式的生成,需要使用其他庫時,需要在連結時加以指定。這就需要用到gcc 的-l與-L選項。
假設一個程式叫做main.c,它編譯成可執行程式不光需要系統的標準庫,還需要一個庫:libfoo.a 且這個libfoo.a與main.c在同一個目錄,那麼在編譯main.c時,需要以下命令:
gcc -o main -L. main.c -lfoo
複製程式碼
注意:
- -L告訴gcc編譯器,當前可以從哪個目錄查詢庫檔案,此處-L後面跟了一個**點‘.’**表示當前目錄。
- -l選項,告訴編譯器需要連線的庫名。這裡並沒有寫“lib”字首和“.a字尾”。-lfoo就是代表指定libfoo.a庫參與連結。
更加詳細的內容參考《程式設計師的自我修養》
4、總結
今天學習了gcc的簡單概念,與gcc的常用的引數選項。