(程式設計師的自我修養)瞭解程式執行之前都幹了些什麼

Swift_Xu發表於2017-12-14

前言

開篇先介紹兩篇文章,一篇是 大神bestsWifter《程式設計師的自我修養》讀書總結

還有一篇我感覺也是非常好的文章,作者教會我們對待學習的態度 為什麼知乎Live,分答,得到都不是乾貨?-20171123

其實下面我要記錄下的內容也都是<程式設計師的自我修養>這本書裡面的內容,這裡記錄下來

正題

C 語言的經典,“Hello,World”程式幾乎是每個程式設計師閉著眼睛都能寫出來的,編譯執行一氣呵成。

#include <stdio.h>

int main()
{
    printf("Hello world\n")
    return 0;
}
複製程式碼

在Linux下,當我們使用GCC來編譯Hello World程式時,只須使用最簡單的命令(假設原始碼的檔名為hello.c)

$gcc hello.c
$./a.out
Hello World
複製程式碼

事實上,上述過程可以分解為4個步驟,分別是預處理(Prepressing),編譯(Compilation),彙編(Assembly)和連結(Linking)

  • GCC 編譯過程分解圖
    GCC 編譯過程分解圖

預編譯

首先是原始碼檔案hello.c 和相關的標頭檔案,如stdi.h 等被預編譯器cpp預編譯成一個.i檔案,對於C++程式來說,它的原始碼檔案的副檔名可能是.cpp 或.cxx ,標頭檔案的副檔名可能是.hpp,而預編譯後的副檔名是.ii。第一步預編譯的過程相當於如下命令(-E表示只進行預編譯)
複製程式碼
$gcc -E hello.c -o hello.i
複製程式碼

或者

$cpp hello.c > hello.i
複製程式碼

預編譯過程主要處理那些原始碼中的以“#”開始的預編譯指令,比如“#include” “#define”等,主要處理規則如下:

將所有的“#define”刪除,並且展開所有的巨集定義

處理所有條件預編譯指令,比如“#if” “#ifdef” “#elif” “#endif” “#else”

處理“#include”預編譯指令,將被包含的檔案插入到該預編譯指令的位置。注意,這個過程是遞迴進行的,也就是說被包含的檔案可能還包含其他檔案。

刪除所有的註釋

新增行號和檔名標識,比如#2 “hello.c” 2, 以便於編譯時編譯器產生除錯除錯用的行號資訊及用於編譯時產生編譯錯誤或警告時能夠顯示行號

保留所有的#pragma編譯器指令,因為編譯器需要使用他們

經過編譯後的.i檔案不包含任何巨集定義,因為所有的巨集已經被展開,並且包含的檔案也已經被插入到.i 檔案中。所以當我們無法判斷巨集定義是否正確或標頭檔案包含是否正確時,可以檢視編譯後的檔案來確定問題。

編譯

編譯過程就是把預處理完的檔案進行一系列的詞法分析,語法分析,語義分析及優化後生成相應的彙編程式碼檔案。上面的編譯過程相當於如下命令

$gcc -S hello.i -o hello.s
複製程式碼

彙編完成後生成一個.s 檔案。對於C語言的程式碼來說,這個預編譯和編譯過程的程式是cc1,對於C++來說,有對應的程式叫cc1plus,對於Object-C 是cc1obj。實際上gcc這個命令只是這些後臺程式的包裝,它會根據不同的引數要求去呼叫預編譯程式cc1,彙編器as,聯結器Id

彙編

彙編器是將彙編程式碼轉變成機器可以執行的命令,每一行彙編語句幾乎都對應一條機器指令。所以彙編器的彙編過程相對於編譯器來講比較簡單,它沒有複雜的語法,也沒有語義,也不需要指令優化,只是根據彙編指令和機器指令的對照表一一翻譯就可以了,“彙編”這個名字也是來源於此。彙編的過程我們可以呼叫匯編器as來完成:

$as hello.s -o hello.o
複製程式碼

或者

$gcc -c hello.s -o hello.o
複製程式碼

或者使用gcc命令從C原始碼檔案開始,經過預編譯,編譯,彙編直接輸出目標檔案(Object File):

$gcc -c hello.c -o hello.o
複製程式碼

連結

連結通常是一個讓人比較費解的過程,為什麼彙編器不直接輸出可執行檔案而是輸出一個目標檔案?連結過程到底包含了什麼內容?為什麼要連結? 直接呼叫Id 執行Hello World 程式:

#ld -static /usr/lib/crt1.o /usr/lib/crti.o
/usr/lib/gcc/i468-linux-gnu/4.1.3/crtbeginT.o
-L/usr/lib/gcc/i468-linux-gun/4.1.3 
-L/usr/lib -L/lib hello.o --start-group-lgcc -lgcc_eh -1c --end-group 
/usr/lib/gcc/i468-linux-gun/4.1.3/crtend.o 
/usr/lib/crtn.o
複製程式碼

如果把上面的路徑都省略掉,那就是

ld -static crt1.o crti.o crtbeginT.o hello.o -start-group -lgcc -lgcc -lgcc_eh -1c-end-group crtend.o crtn.o
複製程式碼

可以看到,我們將一大堆檔案連結起來才可以得到"a.out",既是最終的可執行檔案。估計大家對crt1.o crti.o crtbeginT.o crtend.o crtn.o 這些檔案是什麼,有什麼作用, -lgcc -lgcc_eh -lc 這些又是什麼引數,為什麼要將他們和hello.o 連結起來才可以得到可執行檔案 ,有很多疑惑又或者不知道,那麼就讓我來告訴你們,其實我也不知道。。。 哈哈。。。所以才要學習。不過從<程式設計師的自我修養> 這本書裡面大家可以找到答案。

程式設計師的自我修養 這本書非常值得拜讀,之前只是大致的讀了一些,現在忘的也差不多了,趁著這次從某書過來的一次機會再重新讀一遍,並把一些重要的地方記錄下來,方便加深記憶和理解。孔老夫子說的好,溫故而知新,看一遍可能只是有個印象,兩邊是記住,三遍可能才能從中理解到真正的含義。當然不排除有些大神,看一遍就可以了。

共勉

相關文章