還是從HelloWorld開始說吧...
#include <stdio.h> main( argc, *
可使用以下命令,直接從源文件生成可執行文件
linux:
gcc -lstdc++ Hello.cpp -o Hello. g++ Hello.cpp -o Hello.
:
windows:
cl Hello.cpp /link -:Hello.exe
:主要是做一些代碼文本的替換工作。(該替換是一個的過程。)
linux:
cpp Hello.cpp >-E Hello.cpp -++ -E Hello.cpp -o Hello.i
行號與文件名標識解釋:
# __u_long;
不產生行號與文件名標識:
cpp -P Hello.cpp >-E -P Hello.cpp -++ -E -P Hello.cpp -o Hello.i
windows:
cl /E Hello.cpp > Hello.i
行號與文件名標識解釋:
283 "C:\\Program Files\\Microsoft Visual Studio\\VC98\\include\\stdio.h" __cdecl clearerr(FILE * __cdecl fclose(FILE * __cdecl _fcloseall();
不產生行號與文件名標識:
cl /EP Hello.cpp > Hello.i
:把預處理完的文件進行一系列(lex)、(yacc)、語義分析及後生成匯編代碼,這個過程是程序構建的核心部分。
linux:
/usr/lib/gcc/i586-suse-linux/../cc1 Hello.cpp
.file ,@progbits
對於含c++的特性的cpp文件,應使用cc1plus進行編譯,或使用gcc命令來編譯(會通過後綴名來選擇調用cc1還是cc1plus)
/usr/lib/gcc/i586-suse-linux/./-S Hello.cpp -++ -S Hello.cpp -o Hello.s
windows:
cl /FA Hello.cpp Hello.asm
vc6生成出來的Hello.asm文件如下:
FLAT, FLAT, OFFSET esp,
:匯編代碼->機器指令。
linux:
Hello.s --c Hello.cpp -++ -c Hello.cpp -o Hello.o
windows:
cl /c Hello.cpp > Hello.obj
至此,產生的目標文件在結構上已經很像最終的可執行文件了。
:這裡講的鏈接,嚴格說應該叫靜態鏈接。多個目標文件、庫->最終的可執行文件(拼合的過程)。
可執行文件分類:
linux:
ld - /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i586-suse-linux/./crtbeginT.o -L/usr/lib/gcc/i586-suse-linux/./ -L/usr/lib -L/lib Hello.o --start-group -lgcc -lgcc_eh -lc --end-group /usr/lib/gcc/i586-suse-linux/./crtend.o /usr/lib/crtn.o -o Hello.
windows:
link /subsystem:console /:Hello.exe Hello.obj
靜態庫本質上就是包含一堆中間目標文件的壓縮包,就像zip等文件一樣,裡面的各個中間文件包含的外部符號地址是沒有被鏈接器修正的。
linux:
ar -t libc.a
windows:
lib /list libcmt.lib
linux:【】
ar -x /usr/lib/libc.a
windows:【】
lib libcmt.lib /extract:build\intel\mt_obj\atof.obj
linux:
ar -rf test.a main.o fun.o
windows:
lib /:test.lib main.obj fun.obj
每個函數或變量都有自己獨特的名字,才能避免鏈接過程中不同變量和函數之間的混淆。
在鏈接中,我們將函數和變量統稱為符號,函數名或變量名就是符號名,函數或變量的地址就是符號值。
每一個目標文件都有一個符號表,符號有以下幾種:
(1) 定義在本目標文件的全局符號,可被其他目標文件引用
如:全局變量,全局函數
(2) 在本目標文件中引用的全局符號,卻沒有定義在本目標文件 -- 外部符號(External Symbol)
如:extern變量,printf等庫函數,其他目標文件中定義的函數
(3) 段名,這種符號由編譯器產生,其值為該段的起始地址
如:目標文件的.text、.data等
(4) 局部符號,內部可見
如:static變量
鏈接過程中,比較關心的是上面的與。
linux:
--t Hello.obj
windows上可以安裝MinGW來獲取這些工具。
windows:
dumpbin /symbols Hello.obj
符號修飾實際就是對變量或函數進行重命名的過程,影響命名的因素有:
(1) 語言的不同,修飾規則有差別
如:foo函數,在C語言中會被修飾成_foo,在Fortran語言中會被修飾成_foo_
(2) 面向對象語言(如:C++)引入的特性
如:類、繼承、虛機制、重載、命名空間(namespace)等
函數簽名用於識別不同的函數,包括函數名、它的參數類型及個數、所在的類和命名空間、調用約定類型及其他信息
Visual C++的符號修飾與函數簽名的規則沒有對外公開,但Microsoft提供了一個的API,可以將修飾後名稱轉換成函數原型
使用,強制C++編譯器用C語言的規則來進行符號修飾
g_nTest2 = add( a,
[wiki]
對於C/C++語言來說,編譯器默認函數和初始化了的全局變量為強符號,未初始化的全局變量為弱符號。
GCC可以通過"__attribute__((weak))"來定義任何一個強符號為弱符號。
__attribute__((weak)) ext; __attribute__((weak)) fun1(); fun2() __attribute__((weak)); strong = __attribute__((weak)) weak2 = ;
以上,weak1與weak2是弱符號,strong與main是強符號。
針對強弱符號的概念,鏈接器會按照以下規則處理與選擇被多次定義的全局符號:
(1) 不允許強符號被多次定義,否則鏈接器報符號重復定義的錯誤
(2) 如果一個符號在某個目標文件中是強符號,在其他文件中是弱符號,則選擇強符號
(3) 如果一個符號在所有目標文件中都是弱符號,那麼選擇其中占用空間最大的一個
對外部目標文件的符號引用在目標文件被最終鏈接成可執行文件時,須被正確決議,如果沒有找到該符號的定義,編譯器就會報符號為定義的錯誤,這種被稱為強引用;
與之對應還有一種弱引用,在處理弱引用時,即使該符號未被定義,鏈接器也不會報錯,默認其為0或一個特殊的值。
GCC可以通過"__attribute__((weakref))"來聲明一個外部函數的引用為弱引用。
__attribute__ ((weakref)) (NULL !=
或者程序可以對某些擴展功能模塊的引用定義為弱引用,當我們將擴展模塊與程序鏈接在一起時,功能模塊就可以正常使用;
如果我們去掉了某些功能模塊,那麼程序也可以正常鏈接,只是缺少了相應的功能,這使得程序的功能更加容易裁剪和組合。
#include <stdio.h><math.h> __attribute__((weak)) abs( abs( main( argc, * s = abs(()-, s);
對於鏈接器來說,整個鏈接過程,就是將多個輸入目標文件合成一個可執行二進制文件。
現代鏈接器,基本都是采用的方法:
(1)
掃描所有的輸入目標文件,並且獲得它們的各個段的長度、屬性和位置,並且將輸入目標文件中的符號表中所有的符號定義和符號引用收集起來,統一放到一個全局符號表中。
這一步中,鏈接器將能夠獲得所有輸入目標文件的段長度,並且將它們合並,計算出輸出文件中各個段合並後的長度和位置,並建立映射關系。
(2)
使用上面第一步中收集的所有信息,讀取輸入文件中段的數據、重定位信息(有一個重定位表Relocation Table),並且進行符號解析與重定位、調整代碼中的地址(外部符號)等。
《程序員的自我修養鏈接、裝載與庫》