GCC 程式的編譯過程和連結原理
阿新 • • 發佈:2018-12-17
一、C/C++檔案的編譯過程:
先來看一下gcc的使用方法和常用選項
提示:
gcc --help
Ⅰ、使用方法:
gcc [選項] 檔名
Ⅱ、常用選項:
選項 | 含義 |
---|---|
-v | 檢視gcc編譯器的版本,顯示gcc執行時的詳細過程 |
-o <file> |
Place the output into <file> ;指定輸出檔名為file,這個名稱不能跟原始檔名同名 |
-E | Preprocess only; do not compile, assemble or link;只預處理,不會編譯、彙編、連結 |
-S | Compile only; do not assemble or link;只編譯,不會彙編、連結 |
-c | Compile and assemble, but do not link; 編譯和彙編,不會連結 |
一個C/C++檔案要經過預處理(preprocessing)、編譯(compilation)、彙編(assembly)、和連線(linking)才能變成可執行檔案。
以下列程式為例,追層來分析編譯過程。 hello.c:
#include <stdio.h>
#define MAX 20
#define MIN 10
#define _DEBUG
#define SetBit(x) (1<<x)
int main(int argc, char* argv[])
{
printf("Hello World \n");
printf("MAX = %d,MIN = %d,MAX + MIN = %d\n",MAX,MIN,MAX + MIN);
#ifdef _DEBUG
printf("SetBit(5) = %d,SetBit(6) = %d\n",SetBit(5),SetBit(6));
printf("SetBit( SetBit(2) ) = %d\n",SetBit( SetBit(2) ));
#endif
return 0;
}
① 預處理:
gcc -E -o hello.i hello.c
- 預處理就是將要包含(include)的檔案插入原檔案中、將巨集定義展開、根據條件編譯命令選擇要使用的程式碼,最後將這些程式碼輸出到一個“.i”檔案中等待進一步處理。
② 編譯:
gcc -S -o hello.s hello.i
- 編譯就是把C/C++程式碼(比如上面的”.i”檔案)“翻譯”成彙編程式碼。
③ 彙編:
gcc -c -o hello.o hello.s
.o:object file(OBJ檔案) 這裡表現為二進位制目標檔案:
- 彙編就是將第二步輸出的彙編程式碼翻譯成符合一定格式的機器程式碼,在Linux系統上一般表現位ELF目標檔案(OBJ檔案)。
④ 連結:
gcc -o hello hello.o
- 連結就是將彙編生成的OBJ檔案、系統庫的OBJ檔案、庫檔案連結起來,最終生成可以在特定平臺執行的可執行程式。
總結:在編譯過程中。除非使用了”-c”,“-S”,或”-E”選項(或者編譯錯誤阻止了完整的過程),否則統一完整連結步驟。
譬如:gcc hello.c
和gcc -o hello hello.c
都已經完成連結操作。
又如:gcc -c -o hello.o hello.c
二、連結原理:
gcc -c -o hello.o hello.c
不作最後一步連結,得到hello.o二進位制OBJ檔案
gcc -v -o hello hello.o
我們來看一樣連結過程是怎樣的:
[email protected].100ask.org:/work/gcc_options/1th$ gcc -v -o hello hello.o
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16.04.4' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4)
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-v' '-o' 'hello' '-mtune=generic' '-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/5/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper -plugin-opt=-fresolution=/tmp/ccbhavbV.res
-plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s
-plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc
-plugin-opt=-pass-through=-lgcc_s --sysroot=/ --build-id
--eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed
-dynamic-linker /lib64/ld-linux-x86-64.so.2 -z relro
-o hello
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
-L/usr/lib/gcc/x86_64-linux-gnu/5
-L/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu
-L/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib
-L/lib/x86_64-linux-gnu -L/lib/../lib
-L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib
-L/usr/lib/gcc/x86_64-linux-gnu/5/../../..
hello.o
-lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
[email protected].100ask.org:/work/gcc_options/1th$
- crt1.o、crti.o、crtbegin.o、crtend.o、crtn.o是gcc加入的系統標準啟動檔案,對於一般應用程式,這些啟動是必需的。
- -lc:連結libc庫檔案,其中libc庫檔案中就實現了printf等函式。
① 動態連結:動態連結使用動態連結庫進行連結,生成的程式在執行的時候需要載入所需的動態庫才能執行。 動態連結生成的程式體積較小,但是必須依賴所需的動態庫,否則無法執行。
預設使用動態連結:
gcc -o hello_shared hello.o
② 靜態連結:靜態連結使用靜態庫進行連結,生成的程式包含程式執行所需要的全部庫,可以直接執行,不過靜態連結生成的程式體積較大。
gcc -static -o hello_static hello.o
③ -nostartfiles
不連結系統標準啟動檔案,而標準庫檔案仍然正常使用:
gcc -v -nostartfiles -o hello hello.o
④ -nostdlib(最常用)
不連結系統標準啟動檔案和標準庫檔案:
gcc -v -nostdlib -o hello hello.o
- 會提示因為沒有連結系統標準啟動檔案和標準庫檔案,而連結失敗。 - 這個-nostdlib選項常用於裸機/bootloader、linux核心等程式,因為它們不需要啟動檔案、標準庫檔案。