一串代碼從寫入編譯器到最後被執行,到底中途經曆了什麼?
c語言中有一個經典的例子helloworld,這是每一個程式員踏入程式設計之路的第一步,哈哈,一入佛門深似海,從此節操是路人。
剛開始我們很懵,不知道什麼,也不敢多問,可是學習了很久之後,你發現你的問題變得多了,為什麼就會被編譯器給列印出來hello world?為什麼必須這樣子寫才能被執行出來hello world?怎麼存放,怎麼執行?等等,你就是一個十萬個為什麼了。
#include<stdio.h>
int main()
{
printf("hello world\n");
return ;
}
好簡單的代碼,相信你是閉着眼睛都能寫出來的。這不是重點,重點是怎麼運作出來的,解決你的為什麼。
這個代碼被寫出來,然後被gcc -o hello hello.c 就可以執行了,哇!真的執行出來了helloworld。剛開始你也會覺得很神奇,但是見多了就覺得沒那麼新奇了。
其實看着很簡單的代碼,可是對于編譯器來說它是怎麼處理的呢?
事實上,上述的過程被分解成了4個部分,分别是預處理、編譯、彙編和連結。
那就一步一步來,反正一口也是吃不了一個胖子的。
預處理:
剛開始#include是被當成了一種慣性來寫出來的。它是什麼,為什麼用?預處理可是專門為它量身打造。
預處理階段stdio.h被預編譯成一個.i的檔案,對于c++程式來說,源代碼是.cpp結尾,頭檔案是.hpp結尾的,那麼被預編譯以後成了.ii。
linux經常使用的指令是:gcc -E hello -o hello.i
預編譯主要的工作(處理以#開頭的預編譯指令):
1.展開所有#define定義的宏;
2.處理所有的條件預編譯指令,如:”if“,”ifdef”,”elif”,”else”,”endif”.
3.#include包含的檔案按插入到檔案指定的位置。
4.删除注釋
5.添加行号
6.保留#pragme的預編譯指令。
編譯:
編譯就是就接着預處理的檔案,進行詞法分析、文法分析、語義分析、優化代碼、檢查錯誤、彙總所有的符号。
linux的指令 :gcc -S hello.i -o hello.s
彙編:
根據編譯完之後的.s檔案,将.s檔案轉變成彙編指令。
linux指令:gcc -c hello.c -o hello.o
經過彙編後變成了.o檔案。
大緻畫圖說明一下,前面發生的事情。

那麼這個二進制可重定向的二進制檔案,是什麼樣子感覺很神奇呀!沒事神秘還是要有的,一會在說。
連結:
連結可是一個橋梁,一個很重要的角色。
他主要負責:
1.合并個段
2.給符号配置設定位址(重定向)
3.符号解析
從此之後hello變成了可執行檔案。
剛才前面不是留下了一個疑問,現在來看一下,目标檔案經過一個連結之後,變成了一個可執行檔案,他們兩個有什麼不一樣嗎?(不就是有了位址有什麼了不起的)。
二進制可重定向目标檔案
目标檔案:
紅色部分是和二進制可重定向目标檔案的差別:
回歸到原來的主幹道上吧!不要迷路了!
将目标問價加載到虛拟位址空間之後,将main函數的入口位址寫入pc寄存器。
還沒有執行,目标檔案被加載都了自己的虛拟位址空間。但是作業系統不認識這個虛拟位址呀!
我們得大費周章的加載到實體記憶體,讓作業系統認識它。怎麼加載呢,大緻是這樣子的。
這下子作業系統才認識,hello才會被執行,現在看來,是不是一個helloworld要被執行,也是很費勁的,沒你想得那麼簡單。
是以好好愛惜你的作業系統吧!