1. 程式人生 > >C/C++編譯過程

C/C++編譯過程

這裡寫圖片描述

編譯過程 + 連結過程(編譯過程是大括號,其餘為連結)

編譯過程包括編譯和彙編。.

這裡寫圖片描述
.c/.cpp 編譯預處理–E .i 編譯-S .s(彙編程式碼) 彙編-c .o(目標檔案) 連結-o .exe(可執行檔案)

1、編譯預處理(前處理器)

gcc -E test.c -o test.i // 將test.c預處理輸出test.i檔案。.i檔案裡還是c語言

在這個階段,前處理器在原始碼上執行一些文字操作,必不可少的有:標頭檔案展開、巨集替換、去掉註釋和條件編譯等過程。
1. 巨集定義替換。
2. 條件編譯。如#ifdef,#ifndef,#else,#elif,#endif,等等。一般用在標頭檔案中,防止重複編譯。
3. 標頭檔案展開。如#include “FileName”或者#include 等。
4. 特殊符號。

__FINE__    //進行編譯的原始檔
__LINE__    //檔案被編譯的當前行號
__ DATE__  //檔案被編譯的日期
__TIME__   //檔案被編譯的時間

printf("file:%s\nline:%d\ndate:%s\ntime:%s\n",
        __FILE__, __LINE__, __DATE__, __TIME__);

2、編譯(編譯器)+ 優化

gcc -S test.i (-o test.s)// 將預處理輸出檔案test.i編譯成test.s檔案。.s檔案裡是組合語言

經過預編譯得到的輸出檔案中,就只有常量,如數字、字串、變數的定義,以及C語言的關鍵字,如main,if,else,for,while,{,},+,-,*,\

,等等。

編譯就是通過詞法分析和語法分析,在確認所有的指令都符合語法規則之後,將其翻譯成彙編程式碼

優化處理是編譯系統中一項比較艱深的技術。
經過優化得到的彙編程式碼必須經過彙編程式的彙編轉換成相應的機器指令,方可能被機器執行。

3、彙編

gcc -c test.s (-o test.o) // 將編譯輸出檔案test.s彙編成test.o目標檔案。
gcc -c test.i -o test.o // 將預處理輸出檔案test.i編譯成test.o目標檔案

彙編過程實際上指把彙編程式碼翻譯成目標機器指令的過程。
每一個C語言源程式,都將最終經過這一處理而得到相應的目標檔案。目標檔案中所存放的也就是與源程式等效的目標的機器語言程式碼。
目標檔案由段組成。通常一個目標檔案中至少有兩個段:
1. 程式碼段:該段中所包含的主要是程式的指令。該段一般是可讀和可執行的,但一般卻不可寫。
2. 資料段:主要存放程式中要用到的各種全域性變數或靜態的資料。一般資料段都是可讀,可寫,可執行的。

4、連結

gcc test.o -o test // 將.o連結成可執行檔案
gcc test.c -o test // 將原始檔直接編譯彙編連結成成執行檔案
彙編得到的目標檔案並不能直接被電腦執行,因為一個目標檔案裡可能引用了其他檔案的變數或某個庫的函式,所以需要連結,將有關的目標檔案彼此相連線,也即將在一個檔案中引用的符號同該符號在另外一個檔案中的定義連線起來,使得所有的這些目標檔案成為一個能夠被作業系統裝入執行的統一整體
連結分兩種:靜態連結和動態連結

靜態連結

在這種連結方式下,函式的程式碼將從其所在的靜態連結庫中被拷貝到最終的可執行程式中。這樣該程式在被執行時這些程式碼將被裝入到該程序的虛擬地址空間中。
.lib是靜態庫,編譯的時候程式碼直接插入到可執行程式。使用者通過標頭檔案找到庫檔案中函式實現的程式碼從而把這段程式碼連結到使用者程式中去。
然而靜態連結,簡而言之就是將程式執行所需的指令和資料全部裝入記憶體中使得記憶體可以順利執行,然而這種方式雖然簡單但又粗暴,極大程度的浪費記憶體空間。針對程式執行時的區域性性原理,我們可以將不常用的資料存放在磁碟中,只將不同的程式所需的不同模組裝入記憶體,這樣便有效的利用記憶體空間,即產生動態連結(覆蓋裝入、頁對映)。

動態連結

動態連結的產生,則需要對應的動態庫,由於動態庫中的各模組可供使用者選擇使用從而也實現了模組資源共享,因此動態庫又稱共享庫。
在此種方式下,函式的程式碼被放到稱作是動態連結庫或共享物件的某個目標檔案中。連結程式此時所作的只是在最終的可執行程式中記錄下共享物件的名字以及其它少量的登記資訊。在此可執行檔案被執行時,動態連結庫的全部內容將被對映到執行時相應程序的虛地址空間。動態連結程式將根據可執行程式中記錄的資訊找到相應的函式程式碼。
.dll是動態庫,編譯的時候,只是產生一些呼叫DLL內程式碼的匯入表,真正執行的時候是呼叫的DLL內的程式碼。

動態連結庫是程式執行時載入的庫,當動態連結庫正確安裝後,所有的程式都可以使用動態庫來執行程式。動態連結庫是目標檔案的集合,目標檔案在動態連結庫中的組織方式是按照特殊方式形成的。庫中函式和變數的地址是相對地址,不是絕對地址,其真實地址在呼叫動態庫的程式載入時形成。
動態連結庫的名稱有別名(soname), 真名(realname)和連結名(linker name)。別名由一個字首lib,然後是庫的名字,再加上一個字尾“.so”構成。真名是動態連結庫真實名稱,一般總是在別名的基礎加上一個小版本號,釋出版本等構成。除此之外,還有一個連結名,即程式連結時使用的庫的名字。
在動態連結庫安裝的時候,總是複製檔案到某個目錄下,然後用一個軟連線生成別名,在庫檔案進行更新的時候,僅僅更新軟連結即可。

二者區別

區別主要在於庫中程式碼被載入的時刻不同。
(1)靜態庫:在程式編譯時期會被連線到目的碼中,由於靜態連結只會生成一個可執行程式碼,因此目標程式執行時不需要再載入。
(2)共享庫:在程式編譯時期僅簡單引用,不會被連線到目的碼中,而是在程式執行時才被載入,因此在程式執行時需要動態庫存在。“以時間換取空間”

對於可執行檔案中的函式呼叫,可分別採用動態連結或靜態連結的方法。使用動態連結能夠使最終的可執行檔案比較短小,並且當共享物件被多個程序使用時能節約一些記憶體,因為在記憶體中只需要儲存一份此共享物件的程式碼。但並不是使用動態連結就一定比使用靜態連結要優越。在某些情況下動態連結可能帶來一些效能上損害。

.a代表傳統的靜態函式庫(也稱作歸檔檔案:archive)
.so代表共享函式庫(共享庫就是動態庫)

標頭檔案位置一般在/usr/include
庫檔案位置一般在/usr/lib

二、建立靜態庫檔案 .a:

靜態庫,還是動態庫,都是由.o檔案建立的。
1. 建立hello.c, hello.h 和 app.c。
hello.c定義了一個hello函式,app.c包含了hello.h,然後呼叫了hello函式。
2. 建立.o檔案
gcc -c hello.c -> hello.o
3. 由.o檔案建立靜態庫檔案.a (庫名要在真名前加lib,後加.a)
ar cr libmyhello.a hello.o // 一個或幾個.o -> libmyhello.a
4. 使用靜態庫檔案
只需要在使用到這些公用函式的源程式中包含其原型宣告,然後在用gcc命令生成目標檔案時指明靜態庫名-L/-l),gcc將會從靜態庫中將公用函式連線到可執行檔案中。
注意,gcc會在靜態庫名前加上字首lib,然後追加副檔名.a得到的靜態庫檔名來查詢靜態庫檔案。

在app.c中,我們包含了靜態庫的標頭檔案hello.h,然後在主程式app中直接呼叫公用函式hello。下面先生成目標程式app,然後執行hello程式看看結果如何。
gcc -o app app.c -L. -lmyhello //-o指定輸出
gcc app.c -o app -L. -lmyhello
gcc app.c -L. -lmyhello -o app //-o file在哪都可以
靜態庫中的hello函式已經連線到可執行檔案app中了,此時刪掉靜態庫,app也能執行。

-L後緊跟庫檔案所在目錄(此處為當前資料夾.),
-l後緊跟庫檔案的真名
另外-I引數後緊跟標頭檔案目錄(有需要的話)
例子:gcc -o hello hello.c -I/home/hello/include -L/home/hello/lib -lworld

三、動態庫的生成及使用

靜態庫有很多缺點,所以有必要使用動態庫。

  1. 動態連結庫是程式執行時載入的庫,當動態連結庫正確安裝後,所有的程式都可以使用動態庫來執行程式。動態連結庫是目標檔案的集合,目標檔案在動態連結庫中的組織方式是按照特殊方式形成的。庫中函式和變數的地址是相對地址,不是絕對地址,其真實地址在呼叫動態庫的程式載入時形成。
  2. 動態連結庫的名稱有別名(soname), 真名(realname)和連結名(linker name)。別名由一個字首lib,然後是庫的名字,再加上一個字尾“.so”構成。真名是動態連結庫真實名稱,一般總是在別名的基礎加上一個小版本號,釋出版本等構成。除此之外,還有一個連結名,即程式連結時使用的庫的名字。
  3. 在動態連結庫安裝的時候,總是複製檔案到某個目錄下,然後用一個軟連線生成別名,在庫檔案進行更新的時候,僅僅更新軟連結即可。

生成和使用動態庫:
1. 建立源程式hello.c hello.h
2. 生成目標檔案.o,但是此時要加編譯器選項-fPIC和連結器選項-shared
gcc -c -fPIC hello.c -o hello.o
3. 生成動態庫檔案.so
gcc -shared -fPIC -o libmyhello.so hello.o

以上兩步也可以一次完成:

gcc -fPIC -shared hello.c -o libmyhello.so
“PIC”命令列標記告訴GCC產生的程式碼不要包含對函式和變數具體記憶體位置的引用,這是因為現在還無法知道使用該訊息程式碼的應用程式會將它連線到哪一段記憶體地址空間。這樣編譯出的hello.o可以被用於建立共享連結庫。建立共享連結庫只需要用GCC的”-shared”標記即可。
4. 在程式中使用動態庫
在程式中使用動態庫和使用靜態庫完全一樣,也是在使用到這些公用函式的源程式中包含其原型宣告,然後在用gcc命令生成目標檔案時指明動態庫名進行編譯。
gcc -o app app.c -L. -lmyhello
輸出:cannot open shared object file: No such file or directory
需要將庫檔案libhello.so複製到資料夾/usr/lib下。
./app 即可成功。

四、靜態庫動態庫搜尋路徑先後順序

靜態庫
  1. ld會去找GCC命令中的引數-L
  2. 再找gcc的環境變數LIBRARY_PATH
  3. 再找預設目錄 /lib /usr/lib /usr/local/lib
動態庫

動態庫的搜尋路徑搜尋的先後順序是:
  1.編譯目的碼時指定的動態庫搜尋路徑;
  2.環境變數LD_LIBRARY_PATH指定的動態庫搜尋路徑;
  3.配置檔案/etc/ld.so.conf中指定的動態庫搜尋路徑;
  4.預設的動態庫搜尋路徑/lib /usr/lib。

以上4個順序,當找不到.so檔案時對應4種解決方法:
1. gcc編譯時加入-Wl,-rpath引數
2. 修改該變數的值為.so所在目錄即可: export LD_LIBRARY_PATH=$(pwd)
3. 把.so所在目錄增加到該檔案也可。
4. 複製.so到預設目錄也可。