軟件開發工具——GCC
GCC(GNU Compiler Collection,GNU編譯套裝)能夠編譯C、C++等語言。原本只能處理C語言,但是隨着眾多開發者的加入和GCC自身的發展,如今的GCC已成為可編譯多種語言的編譯器,如C/C++,Java,Fortran,Pascal等,GCC也由原來的GNU C Compiler變為GNU Compiler Collection,能夠在當前CPU計算機平台上,為眾多不同體系的硬件平台開發軟件服務,尤其應用於嵌入式開發領域。
目前Linux默認使用的C編譯器是GCC,具有如下優點:
- 方便進行編譯控制
通過GCC能夠完美地控制整個編譯過程,用戶可以根據需要,在任何階段讓編譯終止或暫停,以檢查或使用編譯器在該階段的輸出信息,並最終生成二進制文件。
- 提供靈活強大的代碼優化功能
通常使用"-On"控制優化配置,n越大,代碼優化越明顯。
- 與GDB調試工具完美結合
GDB調試工具具有查看程序運行狀態、設置斷點、查看表達式、顯示變量等眾多功能。GCC和GDB的結合使用是開發者進行C語言編程的利劍。
- 人性化編譯選項
提供眾多的編譯選項可以定制人性化的編譯過程。例如可以產生調試信息、優化代碼執行過程、選取需要的頭文件等。
- 充足的提示信息
提供很多警告信息,增強了程序的穩定性和可移植性。
GCC編譯過程分為四個階段:預處理、編譯、匯編、鏈接。如下圖所示:
提供幫助:
gcc --heip
GCC常用選項:
選項 | 含義 |
-V | 查看gcc編譯器的版本,顯示gcc執行時的詳細過程 |
-O file | 把輸出文件制定到file中,該選項可以輸出匯編文件、目標文件以及可執行文件。 |
-E | 只對源文件進行預處理,不做編譯、匯編及鏈接,GCC會忽略任何不需要預處理的輸入文件。 |
-S | 只進行編譯,不做匯編及鏈接,對於每個輸入的非匯編語言文件,輸出文件都是匯編語言文件。 |
-c | 只進行匯編,不做鏈接,匯編成源文件的目標文件,默認狀態下生成.o文件,GCC忽略-c選項后面任何無法識別的輸入文件。 |
例如新建文件hello.c如下:
#include <stdio.h> int main() { printf("hello, world\n"); }
第一步 預處理階段
執行命令: gcc -o hello.i -E hello.c
預處理器cpp根據以字符開頭#開頭的命令,修改原始C程序。比如hello.c中的第一行為 #include <stdio.h>,預處理器便將stdio.h的內容直接插入到程序中。預處理之后得到文本文件hello.i,打開如下:
# 1 "hello.c" # 1 "<built-in>" # 1 "<command-line>" # 31 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 32 "<command-line>" 2 # 1 "hello.c" # 1 "/usr/include/stdio.h" 1 3 4 # 27 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 1 3 4 # 33 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 3 4 # 1 "/usr/include/features.h" 1 3 4 # 424 "/usr/include/features.h" 3 4
.........
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 868 "/usr/include/stdio.h" 3 4
# 2 "hello.c" 2
# 3 "hello.c"
int main()
{
printf("hello, world\n");
}
預處理就是將要包含(include)的文件插入原文件中、將宏定義展開、根據條件編譯命令選擇要使用的代碼,最后將這些代碼輸出到一個“.i”文件中等待進一步處理。
第二步 編譯階段
執行命令: gcc -o hello.s -S hello.i
編譯器ccl將文本文件hello.i 翻譯為hello.s,這個文件里面包含一個匯編程序,就是把C/C++代碼(比如上面的".i"文件)“翻譯”成匯編代碼。匯編語言是非常有用的,因為它將不同高級語言的不同編譯器提供了通用的輸出語言。如下所示:
.file "hello.c" .text .section .rodata .LC0: .string "hello, world" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 leaq .LC0(%rip), %rdi call puts@PLT movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0" .section .note.GNU-stack,"",@progbits
第三步 匯編階段
執行命令:gcc -c -o hello.o hello.s
匯編器as將hello.s翻譯成機器語言保存在hello.o中。匯編就是將第二步輸出的匯編代碼翻譯成符合一定格式的機器代碼,在Linux系統上一般表現位ELF目標文件(OBJ文件)。
第四步 鏈接階段
執行命令: gcc -o hello hello.o
鏈接就是將匯編生成的OBJ文件、系統庫的OBJ文件、庫文件鏈接起來,最終生成可以在特定平台運行的可執行程序。鏈接分為靜態鏈接和動態鏈接兩種。
動態鏈接:動態鏈接使用動態鏈接庫進行鏈接,生成的程序在執行的時候需要加載所需的動態庫才能運行。 動態鏈接生成的程序體積較小,但是必須依賴所需的動態庫,否則無法執行。
靜態鏈接:靜態鏈接使用靜態庫進行鏈接,生成的程序包含程序運行所需要的全部庫,可以直接運行,不過靜態鏈接生成的程序體積較大。
結果得到hello 可執行文件,可以被加載到內存中由系統執行。
./hello
完整的執行過程為:
keegen@keegensCP:~/test$ touch hello.c keegen@keegensCP:~/test$ vim hello.c keegen@keegensCP:~/test$ gcc -o hello.i -E hello.c keegen@keegensCP:~/test$ ls -a . .. hello.c hello.i keegen@keegensCP:~/test$ gcc -o hello.s -S hello.i keegen@keegensCP:~/test$ ls -a . .. hello.c hello.i hello.s keegen@keegensCP:~/test$ gcc -c -o hello.o hello.s keegen@keegensCP:~/test$ ls hello.c hello.i hello.o hello.s keegen@keegensCP:~/test$ gcc -o hello hello.o keegen@keegensCP:~/test$ ls hello hello.c hello.i hello.o hello.s keegen@keegensCP:~/test$ ./hello hello, world
總結編譯器的編譯過程:
源文件-->預處理-->編譯/優化-->匯編-->鏈接 -->可執行文件
在編譯過程中。除非使用了"-c",“-S”,或"-E"選項(或者編譯錯誤阻止了完整的過程),否則統一完整鏈接步驟。如運行
gcc hello.c 或 gcc -o hello hello.c
keegen@keegensCP:~/test$ rm -rf hello hello.i hello.o hello.s keegen@keegensCP:~/test$ ls hello.c keegen@keegensCP:~/test$ gcc hello.c keegen@keegensCP:~/test$ ls a.out hello.c keegen@keegensCP:~/test$ ./a.out hello, world keegen@keegensCP:~/test$ gcc -o hello hello.c keegen@keegensCP:~/test$ ls a.out hello hello.c keegen@keegensCP:~/test$ ./hello hello, world