在多個文件編譯之後,每個文件生成了一個.o文件,然後是使用鏈接器,將.o文件鏈接爲一個可執行文件的。鏈接器爲了創建可執行文件,必須完成兩個任務:
1.符號解析:目標文件定義和引用符號。符號解析的目的是將每個符號引用和符號定義聯繫起來。
2.重定位:彙編器和編譯器生成從地址零開始的代碼和數據段。鏈接器通過把每個符號定義與一個存儲器位置(虛存)聯繫起來,然後修改所有對這些符號的引用,使得它們指向這個存儲器的位置,從而重定向這些段。
這兩句話我的理解是1.將不同.o文件中的變量聲明和變量定義聯繫起來,2.因爲每個編譯出來的.o文件都是從地址零開始的,所以當這些.o文件鏈接爲一個文件是,就需要將地址做相應的調整,保證程序執行的正確性。
舉一個例子:
//main.c
void swap();
int buf[2] = {1,2};
int main()
{
swap();
return 0;
}
//swap.c
extern int buf[];
int *bufp0 = &buf[0];
int *bufp1;
void swap()
{
int temp;
bufp1 = &buf[1];
temp = *bufp0;
*bufp0 = *bufp1;
*bufp1 = temp;
}
上述的小程序是交換數組中的兩個數字的位置。
編譯的流程圖爲:
每一個.c文件都會經過編譯生成一個.o文件,所有的.o文件經過連接,生成一個可執行文件。
靜態連接:
我們使用的標準庫,包含了我們經常所用的標準I/O,串操作,整數算術函數等等。下面說下靜態連接的必要性:
如果沒有靜態庫,我們將剛纔的所有庫函數都編譯爲一個.o文件,當要使用是,我們將這個.o文件連接到我們使用庫函數的cpp文件中,例如gcc main.c libc.o. 這樣存在的問題,是系統的每一個可執行文件都含有一份標準函數的完全拷貝,而不是我需要哪些函數就有哪些函數。所以靜態庫應運而生。在鏈接靜態庫時,鏈接器值拷貝被程序引用的目標模塊,這樣就減少了可執行文件在磁盤和存儲器中的大小。
舉個例子:
//addvec.c
void addvec(int *x,int *y, int *z, int n)
{
int i ;
for(i = 0; i < n; i ++)
z[i] = x[i] + y[i];
}
//multvec.c
void multvec(int *x, int *y, int *z, int n)
{
int i;
for(i = 0; i < n; i ++ )
z[i] = x[i] * y[i];
}
編譯這兩個.c文件,創建靜態庫
gcc -c addvec.c multvec.c
ar rcs libvector.a addvec.o multvec.o 生成靜態庫libvector.a其中包括兩個.o文件
//main2.c
#include <stdio.h>
#include "vector.h"
int x[2] = {1,2};
int y[2] = {3,4};
int z[2];
int main()
{
addvec(x,y,z,2);
printf("z = [%d %d]\n", z[0],z[0]);
return 0;
}
//vector.h
void addvec(int*, int*,int*,int);
void multvec(int*,int*,int*,int);
先編譯main.o
gcc -O2 -c main2.c
生成可執行文件
gcc -static -o p2 main2.o ./libvector.a
-static參數告訴編譯器,鏈接器應該構建一個完全連接的可執行目標文件,它可以加載到存儲器並運行,在加載時無須更進一步的連接了。當鏈接器運行時,他判定addvec.o定義的addvec符號是被main.o引用的,所以它拷貝addvec.o到可執行文件。因爲程序不引用任何由multvec.o定義的符號,所以鏈接器就不會拷貝這個模塊到可執行文件。鏈接器還會從libc.a拷貝print.o模塊,以及許多C運行時系統中的模塊,如圖:
最後是動態共享庫
我們有了靜態庫,但是仍然有一些問題:
1靜態庫的更新,如果靜態庫需要更新,那麼所有依賴於該庫的可執行程序都需要重新編譯。
2一些常用的函數,例如標準I/O函數print 在運行時,這些函數會被複制到每個運行進程的代碼段中去,造成了極大的浪費
所以我們引入了動態庫。
程序還是我們在靜態庫中使用的文件
編譯靜態庫:
gcc -shared -fPIC -o libvector.so addvec.c multvec.c
-fPIC選項只是編譯器生成與位置無關的代碼,-shared選項指示鏈接器創建一個共享的目標文件
gcc -o p2 main2.c ./libector.so
這樣就創建了一個可執行文件p2.
此時,沒有任何libvector.so的代碼和數據段被真正的拷貝到可執行文件p2中,取而代之的是,鏈接器拷貝了一些重定位和符號表的信息。
附:
查看靜態庫中的函數 nm ****.a
查看動態庫所依賴的其他庫 ldd ****.so
g++選項
-L:指定鏈接庫的路徑,-L. 表示要連接的庫在當前目錄中 -ltest:指定鏈接庫的名稱爲test,編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.so來確定庫的名稱
例如:g++ TestDynamicLibrary.cpp -L../DynamicLibrary -ldynmath