讀書-程式員的自我修養-連結、封裝與庫(10: 第四章:靜态連結(3)--C++問題,ABI,靜态庫libc.a,printf .o,hello.c 整個編譯連結過程)
- 1. C++相關問題
-
- 1.1 C++的兩個問題
- 1.2 c++ 與 ABI
-
- 1.2.1 ABI 定義
- 1.2.2 ABI 和 API 的差別
- 2. 靜态庫連結
-
- 2.1 語言庫概念
- 2.2 ar -t libc.a //檢視靜态庫 libc.a 包含哪些檔案
-
- 2.2.1 objdump -t libc.a grep -w printf //查找 printf 在 libc.a 庫的哪個目标檔案
- 2.2.2 why 一個目标檔案隻包含一個函數?
- 2.3 hello.c 整個編譯連結過程
-
- 2.3.2 gcc -static --verbose hello.c //檢視 hello.c 整個編譯連結過程
1. C++相關問題
1.1 C++的兩個問題
C++的一些語言特性使得必須由編譯器和連結器共同支援才能完成工作。
最主要的有兩個方面。
-
一個是c++的重複代碼消除。
例如模版,外部内聯函數,虛函數表。
- 一個是全局構造與析構。
1.2 c++ 與 ABI
ABI: Application Binary Interface 應用二進制接口
在讨論是不是兩個編譯器編譯的目标檔案能形成一個可執行檔案問題時。
他們需要滿足:采用統一的目标檔案格式,擁有同樣的符号修飾标準、變量的記憶體分布方式相同、函數的調用方式相同等等。
1.2.1 ABI 定義
其中,我們把(編譯器)符号修飾标準、變量記憶體布局,函數調用方式等這些跟可執行代碼二進制相容性相關的内容成為ABI。
1.2.2 ABI 和 API 的差別
ABI 是指二進制層面的接口
API 是指源代碼級别的接口,如printf ,如POSIX 是一個API标準。
2. 靜态庫連結
2.1 語言庫概念
一種語言的開發環境往往會附帶有語言庫(Language Library)。
語言庫又稱為庫函數。這些庫是對作業系統API的包裝。
例如:printf 内部調用的是 write
又如 strlen 就沒有調用系統函數
2.2 ar -t libc.a //檢視靜态庫 libc.a 包含哪些檔案
[email protected]:/usr/lib# ar -t /usr/lib/x86_64-linux-gnu/libc.a
init-first.o
libc-start.o
sysdep.o
version.o
check_fds.o
libc-tls.o
elf-init.o
……
2.2.1 objdump -t libc.a grep -w printf //查找 printf 在 libc.a 庫的哪個目标檔案
libc.a 庫裡面包含了 1400多個目标檔案。
[email protected]:/home/4Chapter# objdump -t /usr/lib/x86_64-linux-gnu/libc.a | grep -w printf
……
printf.o: file format elf64-x86-64
0000000000000000 g F .text 000000000000009e printf
……
[email protected]:/home/4Chapter#
其實,printf.o 也依賴其他的目标檔案,stdout 和 vfprintf
linux 系統庫很複雜,多層級調用。
2.2.2 why 一個目标檔案隻包含一個函數?
- 減少空間浪費
-
詳細解釋
由于運作庫有成百上千個函數,數量非常龐大,每個函數獨立地放在一個目标檔案中可以盡量減少空間的浪費,哪些沒有被利用到的目标檔案/函數就不要連結到最終的輸出檔案中。
2.3 hello.c 整個編譯連結過程
2.3.2 gcc -static --verbose hello.c //檢視 hello.c 整個編譯連結過程
[email protected]:/home# gcc -static --verbose hello.c
Using built-in specs.
COLLECT_GCC=gcc
……
/usr/lib/gcc/x86_64-linux-gnu/5/cc1 -quiet -v -imultiarch x86_64-linux-gnu hello.c
-quiet -dumpbase hello.c -mtune=generic -march=x86-64 -auxbase hello
-version -fstack-protector-strong -Wformat -Wformat-security
-o /tmp/ccudI8qs.s
……
as -v --64 -o /tmp/ccsbu0Fy.o /tmp/ccudI8qs.s
……
/usr/lib/gcc/x86_64-linux-gnu/5/collect2
-plugin /usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so
……
/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/../../../x86_64-linux-gnu/crtn.o
[email protected]:/home#
上面隻留下關鍵步驟:
-
調用 cc1 程式,這個就是 gcc 的c語言編譯器。
它将hello.c編譯成一個臨時的彙編檔案 ccudI8qs.s
-
調用 as 程式, 這個是 GNU 的彙編器。
它将 ccudI8qs.s 彙編成臨時目标檔案 ccsbu0Fy.o,這個其實就是前面的 hello.o.
-
調用 collect2 程式來完成連結。
這個 collect2 可以看做是 ld 連結器的一個包裝。
其中,可以看到 有 crt1.o crti.o crtn.o 等等目标檔案被連結到最終的可執行檔案。