第一步:預處理(也叫預編譯)
gcc -e hello.c -o hello.i
或者 cpp hello.c > hello.i 【cpp是預編譯器】
將所有#define刪除,並且展開所有的巨集定義
處理所有的條件預編譯指令,如#if #ifdef #undef #ifndef #endif #elif
處理#include,將包含的檔案插入到此處,這是乙個遞迴的過程
刪除所有注釋 // /* */
新增行號和檔名標識,以便於編譯時產生的錯誤警告能顯示行號
保留#pragma編譯器指令
第二步:編譯
gcc -s hello.i -o hello.s
將預處理完的.i檔案進行一系列的詞法分析、語法分析、語義分析及優
化後生成響應的彙編**檔案,這是整個程式構建的最核心的部分,也是最複雜的部分
第三步:彙編
gcc -c hello.s -o hello.o或者 as hello.s -o hello.o
彙編是將第二步生成的彙編**程式設計機器可執行的指令,每乙個彙編語句幾乎都對應一條機器指令
第四步:鏈結
鏈結動態庫和靜態庫
目標檔案就是源**經過編譯後但未進行鏈結的那些中間檔案
linux下的 .o檔案就是目標檔案,目標檔案和可執行檔案內容和
格式幾乎都一樣,所以我們可以廣義地將目標檔案和可執行文化
看成一型別檔案。他們都是按照elf檔案格式儲存的
.o檔案、可執行檔案、核心轉儲檔案(core dump)、.so檔案(動態鏈
鏈結庫)
file header.text section.data section.bss section
檔案頭(file header)
描述了整個檔案的檔案屬性,包括目標檔案是否可執行、是靜態鏈結還 是動
態鏈結及入口位址、目標硬體、目標作業系統等資訊、段表(描述檔案中各
個段的偏移位置及屬性等)
**段(.text)
存放了程式源**編譯後生成的機器指令
資料段(.data)
存放已初始化的全域性靜態與非靜態變數和已初始化的區域性靜態變數
.bss段
存放未初始化的全域性變數(全域性靜態和非靜態變數)和區域性靜態變數
但是.bss段只是為這些變數預留位置而已,並沒有內容,所以這些變數
在.bss段中也不佔據空間
使用命令:
objdump -h ***x.o
列印主要段的資訊
objdump -x ***x.o
列印更多的詳細資訊
objdump -s ***.o
將所有段的內容以16進製制方式列印出來
objdump -d ***.o 或者-s
將所有包含指令的段反彙編
objdump -t ***.o
檢視所有的符號以及他們所在段
readelf -h ***.o
檢視.o檔案的檔案頭詳細資訊
readelf -s ***.o
顯示.o檔案中的所有段,即檢視段表
size ***.o
檢視.o檔案中各個段所佔大小
nm ***.o
檢視.o檔案中所有的符號
編譯下面這個test.c程式生成.o檔案,然後檢視.o檔案結構
test.c
/* this is a test code */
/* test.c */
int printf(const char *format, ...);
int g_var2 = 10;
int g_var2;
void func(int i)
int main(void)
gcc -c test.c
然後檢視生成的test.o檔案的結構
objdump -h test.o
行:.text :**段(存放函式的二進位制機器指令)
.data :資料段(存已初始化的區域性/全域性靜態變數、未初始化的全域性靜態變數)
.bss :bss段(宣告未初始化變數所佔大小)
.rodata :唯讀資料段(存放 " " 引住的唯讀字串)
.comment :注釋資訊段
.node.gun-stack :堆疊提示段
列:size:段的長度
file off :段的所在位置(即距離檔案頭的偏移位置)
段的屬性:
contents:表示該段在檔案中存在
alloc :表示只分配了大小,但沒有存內容
我們說.bss段是存放未初始化的全域性變數(靜態與非靜態)和區域性靜態變數的
所以我們程式中的g_var2和stactic_var2應該都在.bss段中被預留位置,所以
.bss段的size應該是8個位元組,但是結果卻是4個位元組,怎麼回事呢?
這就是不用的編譯器實現不一樣的原因了,有些編譯器會將未初始化的全域性非靜態變數放在.bss段,有些則不放,只是預留乙個未定義的全域性變數符號,等到最終鏈結成可執行檔案的時候再在.bss段分配空間。而我的編譯器是沒有將g_var2(全域性未初始化的非靜態變數)放在任何段
下面讓我們真正的檢視一下g_var2
首先,我們使用 readelf -s test.o 檢視段表(主要為了檢視每個段的段號)
然後我們再使用 readelf -s test.o看一下符號表(我們定義的變數名都是符號,包括函式名)
符號表裡會顯示這個符號所在的位置
我們看到static_var1和g_var1所在段的段號為3(3是.data段),static_var2所在段的段號為4(4是.bss段),而g_var2卻沒有被放入任何乙個段,只是用com標記了一下,那這個com表示什麼意思呢?com標記的符號被稱為弱符號,乙個變數名是弱符號,則這個變數的大小在編譯的時候不能被確定,而在鏈結之後才能確定該變數的大小。test.o檔案在鏈結之後,g_var2會被放入.bss段(當然,也只是說明g_var2所需要的空間大小,並不會存放內容),而在程式執行的時候g_var2這樣的變數才會真正去占用記憶體空間
強制將某變數或者某函式放入某個段
__attribute__((section(".data"))) int g_var2; //強制將g_var2放入.data段中
各種變數所在位置總結
全域性已初始化非靜態變數、區域性已初始化靜態變數會被放入.data段
全域性未初始化靜態變數會被放入.bss段
全圖未初始化非靜態變數不會被放入任何乙個段,只是用com標記一下
GCC全過程詳解 剖析生成的 o檔案
第一步 預處理 也叫預編譯 gcc e hello.c o hello.i 或者 cpp hello.c hello.i cpp是預編譯器 將所有 define刪除,並且展開所有的巨集定義 處理所有的條件預編譯指令,如 if ifdef undef ifndef endif elif 處理 incl...
C語言編譯全過程剖析
內容摘要c語言編譯的整個過程是非常複雜的,裡面涉及到的編譯器知識 硬體知識 工具鏈知識都是非常多的,深入了解整個編譯過程對工程師理解應用程式的編寫是有很大幫助的,希望大家可以多了解一些,在遇到問題時多思考 多實踐。一般情況下,我們只需要知道分成編譯和連線兩個階段,編譯階段將源程式 c 轉換成為目標 ...
C語言編譯全過程剖析
內容摘要c語言編譯的整個過程是非常複雜的,裡面涉及到的編譯器知識 硬體知識 工具鏈知識都是非常多的,深入了解整個編譯過程對工程師理解應用程式的編寫是有很大幫助的,希望大家可以多了解一些,在遇到問題時多思考 多實踐。一般情況下,我們只需要知道分成編譯和連線兩個階段,編譯階段將源程式 c 轉換成為目標 ...