第一篇文章
一、前言
最近在看CSAPP(深入理解計算機系統)然後以前也學過C語言,但是從來沒有深究寫好的C程式碼是怎麼編譯再到執行的。
所以現在自己學習,然後記錄下來。
以最常用的hello world!程式為例 程式名: main.c
#include <stdio.h>
int main()
{
printf("Hello world!\n");
return 0;
}
二、C程式編譯過程
hello程式的生命週期是從一個高階C語言程式開始的,為了能夠執行hello.c程式,每一條C語句都被其他程式轉化為一系列的低階機器語言指令。然後這些指令按照一種稱為可執行目標程式的格式打包,以二進位制磁碟檔案的形式存放起來。目標程式也稱為可執行目標檔案。
編譯一個 C程式可以分為四階段:預處理階段 ---> 生成彙編程式碼階段 ---> 彙編階段 ---> 連結階段
各個階段的程式碼可以通過gcc指令來生成
如果沒有gcc可以用下面指令安裝
sudo apt-get build-dep gcc
安裝完之後可以根據以下指令檢視是否安裝成功
gcc --version
安裝好後用下面指令生成中間檔案
gcc main.c 直接生成可執行檔案 a.out
gcc -E main.c -o hello.i 生成預處理後的程式碼
gcc –S main.c -o hello.s 生成彙編程式碼
gcc –c main.c -o hello.o 生成目的碼
三、階段過程
1、預處理階段
gcc -E main.c -o hello.i 生成預處理後的程式碼
前處理器(cpp)根據以字元 # 開頭的命令,修改原始的C程式。比如mian.c中第一行的 #include<stdio.h> 命令就告訴前處理器讀取系統標頭檔案stdio.h的內容,並且把它直接插入程式文字中。同時刪除註釋行,新增行號和檔名標識。這樣就得到了另一個C程式,通常是以 .i 作為副檔名。 所以經過預編譯的 .i 檔案是不包含巨集定義的。
處理完後我們來看看 hello.i 檔案。發現原來的7行程式碼變成了700多行,我們的程式碼在最後面。而前面多出來的程式碼就是 .c 中#include<stdio.h>展開的程式碼。
2、編譯階段
gcc –S main.c -o hello.s 生成彙編程式碼
編譯是將原始檔(hello.i)翻譯成彙編檔案(hello.s)的過程。中間包含詞法、語法分析等步驟,具體過程可以參考《編譯原理》。
開啟彙編程式碼我們會發現裡面有很多以 . 開頭的行,所有這些以 . 開頭的行都是指導彙編器和連結器工作的偽指令。 我們通常可以忽略這些行。
去掉這些行後剩下的部分。
3、彙編階段
gcc –c main.c -o hello.o 生成目的碼
彙編階段是把編譯階段生成的 .s 檔案轉成 .o 的二進位制目的碼。彙編器(as)將 hello.s 翻譯成機器語言指令,把這些指令打包成一種叫做可重定位目標程式的格式,並將結果儲存在目標檔案hello.o中。hello.o檔案是一個二進位制檔案,它的位元組編碼是機器語言指令而不是字元。如果我們在文字編譯器中開啟 hello.o 檔案,看到的將是一堆亂碼。
你非要看就是這樣
4、連結階段
這個階段就是把彙編後的機器指令集變成可以直接執行的檔案,而對目標檔案進行連結主要是因為在目標檔案中可能用到了在其他檔案當中定義的欄位(或者函式),通過連結來把多個不同目標檔案關聯到一起。
hello 程式呼叫了printf 函式,它是每個 C 編譯器都會提供的標準C庫中的一個函式,printf 函式存在於一個名為 printf.o 的單獨預編譯好了的標準檔案中,而這個檔案必須以某種方式合併到我們的 hello.o 程式中,連結器(ld)就負責處理這種合併,結果就得到 hello 檔案,它是一個可執行目標檔案(簡稱:可執行檔案),可以被載入到記憶體中,有系統執行。
這一部分可以參考《程式設計師的自我修養》