前言
GNU CC(簡稱Gcc)是GNU項目中符合ANSI C標準的編譯系統,能夠編譯用C、C++和Object- C等語言編寫的程序。Gcc不僅功能強大,而且可以編譯如C、C++、Object C、Java等多種語言,而且Gcc又是一個交叉平臺編譯器,它能夠在當前CPU平臺上爲多種不同體系結構的硬件平臺開發軟件。本章中的示例均採用Gcc版本爲4.8.2。
Gcc編譯鏈接流程
Gcc編譯鏈接流程分爲四個步驟:
- 預處理(Pre-Processsing)
- 編譯(Compiling)
- 彙編(Assembling)
- 鏈接(Linking)
Gcc指令的一般格式爲:
gcc [option1] compile-files [option2] object-files
其中目標文件可缺省,Gcc默認生成的可執行文件命名爲:編譯文件名.out
下面以簡單的hello world
程序爲例說明Gcc編譯的四個過程:
1 2 3 4 5 6
|
void main(int argc, char* argv[]) { printf("hello world"); return; }
|
預處理過程
option1
爲-E
,生成的目標文件爲.i
(c)或.ii
(c++)後綴的經過預處理的編譯輸入文件,Gcc指令爲:
tly@ubuntu ~> gcc -E test.c -o test.i
生成的預編譯文件內容爲:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
# 1 "test.c" # 1 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 1 "<command-line>" 2 # 1 "test.c" # 1 "/usr/include/stdio.h" 1 3 4 # 27 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/features.h" 1 3 4 # 374 "/usr/include/features.h" 3 4 # 1 "/usr/include/i386-linux-gnu/sys/cdefs.h" 1 3 4 ... 省略 ... extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__)); # 913 "/usr/include/stdio.h" 3 4 extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); # 943 "/usr/include/stdio.h" 3 4 # 2 "test.c" 2 void main(int argc, char* argv[]) { printf("hello world"); return; }
|
Gcc預處理過程把
<stdio.h>
的內容插入到hello.i文件中了
編譯
option1
爲-S
,生成的目標文件爲.s
或.S
後綴的經過編譯但是沒有彙編過的彙編文件,Gcc編譯過程首先要檢查代碼的規範性、是否有語法錯誤等,以確定代碼的實際要做的工作,在檢查無誤後,Gcc把代碼翻譯成彙編語言Gcc指令爲:
tly@ubuntu ~> gcc -S test.i -o test.s
生成的編譯之後的彙編文件內容爲:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
"test.c" .rodata .LC0: "hello world" main main, @function main: .LFB0: pushl %ebp 8 5, -8 movl %esp, %ebp 5 andl $-16, %esp subl $16, %esp movl $.LC0, (%esp) call printf nop leave 5 4, 4 ret .LFE0: main, .-main "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2" "",@progbits .note.GNU-stack,~
|
Gcc編譯過程已經將其轉化爲彙編語言了
彙編
option1
爲-c
,生成的目標文件爲以.o
爲後綴的二進制目標代碼文件,Gcc指令爲:
tly@ubuntu ~> gcc -c test.s -o test.o
生成的彙編之後的目標文件內容爲:
1 2 3
|
^?ELF^A^A^A^@^@^@^@^@^@^@^@^@^A^@^C^@^A^@^@^@^@^@^@^@^@^@^@^@^X^A^@^@^@^@^@^@4^@^@^@^@^@(^@^M^@ ^@U<89>å<83>äð<83>ì^PÇ^D$^@^@^@^@èüÿÿÿ<90>ÉÃhello world^@^@GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2^@^@^@^@^T^@^@^@^@^@^@^@^AzR^@^A|^H^A^[^L^D^D<88>^A^@^@^\^@^@^@^\^@^@^@^@^@^@^@^X^@^@^@^@A^N^H<85>^BB^M^ETÅ^L^D^D^@^@^@.symtab^@.strtab^@.shstrtab^@.rel.text^@.data^@.bss^@.rodata^@.comment^@.note.GNU-stack^@.rel.eh_frame^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^_^@^@^@^A^@^@^@^F^@^@^@^@^@^@^@4^@^@^@^X^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^[^@^@^@ ^@^@^@^@^@^@^@^@^@^@^@ä^C^@^@^P^@^@^@^K^@^@^@^A^@^@^@^D^@^@^@^H^@^@^@%^@^@^@^A^@^@^@^C^@^@^@^@^@^@^@L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@+^@^@^@^H^@^@^@^C^@^@^@^@^@^@^@L^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@0^@^@^@^A^@^@^@^B^@^@^@^@^@^@^@L^@^@^@^L^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@8^@^@^@^A^@^@^@0^@^@^@^@^@^@^@X^@^@^@%^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^A^@^@^@A^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@}^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@U^@^@^@^A^@^@^@^B^@^@^@^@^@^@^@<80>^@^@^@8^@^@^@^@^@^@^@^@^@^@^@^D^@^@^@^@^@^@^@Q^@^@^@ ^@^@^@^@^@^@^@^@^@^@^@ô^C^@^@^H^@^@^@^K^@^@^@^H^@^@^@^D^@^@^@^H^@^@^@^Q^@^@^@^C^@^@^@^@^@^@^@^@^@^@^@¸^@^@^@_^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^A^@^@^@^B^@^@^@^@^@^@^@^@^@^@^@ ^C^@^@°^@^@^@^L^@^@^@ ^@^@^@^D^@^@^@^P^@^@^@ ^@^@^@^C^@^@^@^@^@^@^@^@^@^@^@Ð^C^@^@^T^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^A^@^@^@^@^@^@^@^@^@^@^@^D^@ñÿ^@^@^@^@^@^@^@^@^@^@^@^@^C^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^C^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^D^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^E^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^G^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^H^@^@^@^@^@^@^@^@^@^@^@^@^@^C^@^F^@^H^@^@^@^@^@^@^@^X^@^@^@^R^@^A^@^M^@^@^@^@^@^@^@^@^@^@^@^P^@^@^@^@test.c^@main^@printf^@^L^@^@^@^A^E^@^@^Q^@^@^@^B ^@^@ ^@^@^@^B^B^@^@
|
Gcc彙編成的.o目標文件是亂碼,不過可以通過nm命令查看其符號表:
tly@ubuntu ~> nm test.o
00000000 T main
U printf
鏈接
在成功編譯之後,就進入了鏈接階段,這個hello world
小程序的鏈接過程主要是查找包含的stdio.h
頭文件的printf()
函數的實現(因爲stdio.h
頭文件只包含函數聲明),這個函數實現是在libc.so.6
的庫文件中。在沒有特別指定時,Gcc會到系統默認的搜索路徑/usr/lib
下進行查找,也就是鏈接到libc.so.6
庫函數中去,這樣就能實現函數printf()
了,而這也就是鏈接的作用。
函數庫一般分爲靜態庫和動態庫兩種。
靜態庫:是指編譯鏈接時,把庫文件的代碼全部加入到可執行文件中,因此生成的文件比較大,但在運行時也就不再需要庫文件了。其後綴名一般爲.a
(linux)或.lib
(windows)。
動態庫: 與之相反,在編譯鏈接時並沒有把庫文件的代碼加入到可執行文件中,而是在程序執行時由運行時鏈接文件加載庫,這樣可以節省系統的開銷。動態庫一般爲.so
(linux)或.dll
(windows),如前面所述的libc.so.6就是動態庫。
Gcc在編譯時默認使用動態庫
當然,也可以一次性使用-c
選項,直接生成目標文件test.o
,Gcc指令爲:
tly@ubuntu ~> gcc -c test.c -o test.o
完成了鏈接之後,Gcc就可以生成可執行文件test
,Gcc指令爲:
tly@ubuntu ~> gcc test.o -o test
運行該可執行文件test
:
tly@ubuntu ~> ./test
hello world⏎
Gcc編譯選項分析
Gcc有超過100個的可用選項,一般主要包括以下五種類型選項:
- 總體選項
- 告警和出錯選項
- 優化選項
- 體系結構相關選項
總體選項
選項名 | 選項意義 |
---|---|
-E | 只是編譯不彙編,生成彙編代碼.s |
-S | 只進行預編譯生成.i,不做其他處理 |
-c | 只是編譯不鏈接,生成目標文件.o |
-g | 在可執行程序中包含標準調試信息 |
-o file | 把輸出文件輸出到file裏 |
-v | 打印出編譯器內部編譯各過程的命令行信息和編譯器的版本 |
-I dir | 在頭文件的搜索路徑列表中添加dir目錄 |
-L dir | 在庫文件的搜索路徑列表中添加dir目錄 |
-static | 鏈接靜態庫 |
-llibrary | 鏈接名爲library的庫文件庫 |
對於
-I dir
選項可在頭文件的搜索路徑列表中添加dir目錄。由於Linux中頭文件都默認放到了/usr/include/
目錄下,因此,當用戶希望添加放置在其他位置的頭文件時,就可以通過-I dir
選項來指定(-L dir
類似),這樣,Gcc就會到相應的位置查找對應的目錄<>
表示在標準路徑中搜索頭文件,“ ”
表示在本目錄中搜索,如果把自定義的頭文件#include<my.h>改爲
#include “my.h”,就不需要加上“-I”選項了
-I dir
和-L dir
都只是指定了路徑,而沒有指定文件,因此不能在路徑中包含文件名對於
-llibrary
選項,省去了前綴lib
,它實際上是指示Gcc去連接庫文件liblibrary.so。由於在Linux下的庫文件命名時有一個規定:必須以lib
三個字母開頭。因此在用-l
選項指定鏈接的庫文件名時可以省去lib
三個字母。也就是說Gcc在對-llibrary
進行處理時,會自動去鏈接名爲liblibrary.so
的文件
告警和出錯選項
選項名 | 選項意義 |
---|---|
-ansi | 支持符合ANSI標準的C程序 |
-pedantic | 允許發出ANSI C標準所列的全部警告信息 |
-pedantic-error | 允許發出ANSI C標準所列的全部錯誤信息 |
-w | 關閉所有告警 |
-Wall | 允許發出Gcc提供的所有有用的報警信息 |
修改上述的helloworld
測試程序爲:
1 2 3 4 5 6 7
|
void main(int argc, char* argv[]) { long long tmp; // 增加非ANSI-C類型long long 未使用的臨時變量tmp printf("hello world"); return 0; // 返回錯誤類型int }
|
1.默認無告警和出錯選項情況:
tly@ubuntu ~> gcc -c test.c -o test.o
test.c: In function ‘main’:
test.c:6:3: warning: ‘return’ with a value, in function returning void [enabled by default]
return 0;
^
只識別了main的錯誤返回類型int
2.增加-ansi
選項情況:
tly@ubuntu ~> gcc -c test.c -o test.o -ansi
test.c: In function ‘main’:
test.c:6:3: warning: ‘return’ with a value, in function returning void [enabled by default]
return 0;
^
只識別了main的錯誤返回類型int
3.增加-pedantic
選項情況:
tly@ubuntu ~> gcc -c test.c -o test.o -pedantic
test.c:2:6: warning: return type of ‘main’ is not ‘int’ [-Wmain]
void main(int argc, char* argv[])
^
test.c: In function ‘main’:
test.c:4:8: warning: ISO C90 does not support ‘long long’ [-Wlong-long]
long long tmp;
^
test.c:6:3: warning: ‘return’ with a value, in function returning void [enabled by default]
return 0;
^
識別了main的錯誤返回類型int 和 long long 非 ISO C90 支持類型
4.增加-pedantic-errors
選項情況:
tly@ubuntu ~> gcc -c test.c -o test.o -pedantic-errors
test.c:2:6: error: return type of ‘main’ is not ‘int’ [-Wmain]
void main(int argc, char* argv[])
^
test.c: In function ‘main’:
test.c:4:8: error: ISO C90 does not support ‘long long’ [-Wlong-long]
long long tmp;
^
test.c:6:3: error: ‘return’ with a value, in function returning void
return 0;
^
識別了main的錯誤返回類型int 和 long long 非 ISO C90 支持類型
5.增加-w
選項情況:
tly@ubuntu ~> gcc -c test.c -o test.o -w
屏蔽了告警和出錯信息
6.增加-Wall
選項情況:
tly@ubuntu ~> gcc -c test.c -o test.o -Wall
test.c:2:6: warning: return type of ‘main’ is not ‘int’ [-Wmain]
void main(int argc, char* argv[])
^
test.c: In function ‘main’:
test.c:6:3: warning: ‘return’ with a value, in function returning void [enabled by default]
return 0;
^
test.c:4:13: warning: unused variable ‘tmp’ [-Wunused-variable]
long long tmp;
^
識別了main的錯誤返回類型int 和臨時變量tmp未使用的告警信息
優化選項
選項名 | 選項意義 |
---|---|
-On | n是一個代表優化級別的整數,典型的範圍是從0變化到2或3 |
不同的優化級別對應不同的優化處理工作。
-O 提供基礎級別的優化
-O2 提供更加高級的代碼優化,會佔用更長的編譯時間
-O3 提供最高級的代碼優化
進行調試的時候,最好關閉編譯優化,否則程序自動優化,執行的步驟可能有變化
體系結構相關選項
選項名 | 選項意義 |
---|---|
-mcpu=type | 對不同的CPU使用相應的CPU指令。可選擇的有i386、i486、pentium等 |
-mieee-fp | 使用IEEE標準進行浮點數的比較 |
-mno-ieee-fp | 不使用IEEE標準進行浮點數的比較 |
-msoft-float | 輸出包含浮點庫調用的目標代碼 |
-mshort | 把int類型作爲16位處理,相當於short int |
-mrtd | 將函數參數個數固定的函數用ret NUM返回,節省調用函數的一條指令 |