靜態連結動態連結的連結順序問題和makefile示例

陳小星_絳菱紗影發表於2020-12-17

程式碼檔案初始化

a.h

#ifndef A_H
#define A_H
#include <iostream>
class A
{
public:
 A(){}
    ~A(){}
    void hello();
};
#endif

a.cpp

#include "a.h"

void A::hello()
{
      std::cout << "hello world\n";
}

b.h

#ifndef B_H
#define B_H
#include "a.h"
#include <iostream>

class B
{
    public:
       void sayB();
    private:
      A a;

};
#endif

b.cpp

#include "b.h"

void B::sayB()
{
    std::cout << "B is running\n";
    a.hello();
    std::cout << "B is end\n";
}

c.cpp

#include "b.h"

int main()
{
    std::cout << "C is running\n";
    B b;
    b.sayB();
    std::cout << "C is end\n";
}

.o檔案編譯可執行檔案

目標檔案(.o檔案)編譯,不講究順序

生成各個檔案的.o檔案
[root@localhost test]# g++ -c *.cpp

[root@localhost test]# g++ -o main a.o b.o c.o
[root@localhost test]# g++ -o main c.o b.o a.o
[root@localhost test]# g++ -o main *.o
[root@localhost test]# ./main
C is running
B is running
hello world
B is end
C is end

直接g++ c.cpp -o main是不行的,會缺依賴,少了B::sayB()的實現
[root@localhost test]# g++ c.cpp -o main
/tmp/cc2F6BQH.o: In function `main':
c.cpp:(.text+0x2c): undefined reference to `B::sayB()'
collect2: error: ld returned 1 exit status

直接g++ c.cpp -o main b.o也是不行的,會缺依賴,少了A::hello()的實現
[root@localhost test]# g++ c.cpp -o main b.o
b.o: In function `B::sayB()':
b.cpp:(.text+0x23): undefined reference to `A::hello()'
collect2: error: ld returned 1 exit status

正確
[root@localhost test]# g++ c.cpp -o main b.o a.o
[root@localhost test]# ./main
C is running
B is running
hello world
B is end
C is end

建立靜態連結檔案並編譯

使用 ar 將目標檔案歸檔

ar crv libabc.a a.o b.o c.o

其中:

  • c 如果需要生成新的庫檔案,不要警告
  • r 代替庫中現有的檔案或者插入新的檔案
  • v 輸出詳細資訊

通過 ar t libabc.a 可以檢視 libabc.a 中包含的目標檔案,還可以通過 ar --help 檢視更多幫助。

注意:我們要生成的庫的檔名必須形如 libxxx.a ,這樣我們在連結這個庫時,就可以用 -lxxx。反過來講,當我們告訴編譯器 -lxxx時,編譯器就會在指定的目錄中搜尋 libxxx.a 或是 libxxx.so

靜態連結檔案:在連結階段,將原始檔中用到的庫函式與彙編生成的目標檔案.o合併生成可執行檔案。

靜態連結檔案(.a檔案)連結,講究順序,左邊的lib庫依賴右邊的lib庫

不講究順序的錯誤方式:
[root@localhost test]# g++ -o main aaa/liba.a aaa/libb.a aaa/libc.a 
aaa/libc.a(c.o): In function `main':
c.cpp:(.text+0x2c): undefined reference to `B::sayB()'
collect2: error: ld returned 1 exit status
[root@localhost test]# g++ -o main aaa/*.a
aaa/libc.a(c.o): In function `main':
c.cpp:(.text+0x2c): undefined reference to `B::sayB()'
collect2: error: ld returned 1 exit status

1、使用3個.a靜態連結檔案三種方式
(1)正確順序的依賴
libc.a依賴libb.a,libb.a依賴liba.a
[root@localhost test]# g++ -o main aaa/libc.a aaa/libb.a aaa/liba.a

(2)多次依賴
在使用一些迴圈依賴關係比較複雜的靜態庫時,也可以在連結序列中,讓一個靜態庫出現多次,來解決一些迴圈依賴。
[root@localhost test]# g++ -o main aaa/liba.a aaa/libb.a aaa/libc.a aaa/libb.a aaa/liba.a

(3)使用Xlinker
Xlinker選項是將引數傳給連結器,連結器在處理”-(”和”-)”之間的靜態庫時,是會重複查詢這些靜態庫的,所以就解決了靜態庫查詢順序問題。不過,這種方式比人工提供連結順序的方式效率會低很多。
[root@localhost test]# g++ -o main -Xlinker "-(" aaa/liba.a aaa/libb.a aaa/libc.a -Xlinker "-)"

2、使用2個.a靜態連結檔案
少了liba
[root@localhost test]# g++ c.cpp -o main aaa/libb.a 
aaa/libb.a(b.o): In function `B::sayB()':
b.cpp:(.text+0x23): undefined reference to `A::hello()'
collect2: error: ld returned 1 exit status
連結順序出錯
[root@localhost test]# g++ c.cpp -o main aaa/liba.a aaa/libb.a
aaa/libb.a(b.o): In function `B::sayB()':
b.cpp:(.text+0x23): undefined reference to `A::hello()'
collect2: error: ld returned 1 exit status
正確
[root@localhost test]# g++ c.cpp -o main aaa/libb.a aaa/liba.a 
[root@localhost test]# ./main 
C is running
B is running
hello world
B is end
C is end

3、將a.cpp和b.cpp製作成.a,然後編譯c.cpp時進行連結
[root@localhost test]# ar crv aaa/libab.a a.o b.o
[root@localhost test]# g++ c.cpp -o main -L./aaa -lab
[root@localhost test]# ./main 
C is running
B is running
hello world
B is end
C is end

4、將a.cpp,b.cpp和c.cpp製作成.a,然後直接生成可執行檔案時連結
[root@localhost test]# ar crv aaa/libabc.a a.o b.o c.o
[root@localhost test]# g++ -o main -L./aaa -labc
[root@localhost test]# ./main 
C is running
B is running
hello world
B is end
C is end

5、分別製作三個.a,分別連結,發現不行,原因未知
[root@localhost test]# g++ -o main -L./aaa/tmp -la -lb -lc
/usr/bin/ld: ./aaa/tmp/libc.a(c.o): undefined reference to symbol '__cxa_atexit@@GLIBC_2.2.5'
/usr/bin/ld: note: '__cxa_atexit@@GLIBC_2.2.5' is defined in DSO /usr/lib64/libc.so.6 so try adding it to the linker command line
/usr/lib64/libc.so.6: could not read symbols: Invalid operation
collect2: error: ld returned 1 exit status
[root@localhost test]# g++ -o main -L./aaa/tmp -lc -lb -la
/usr/bin/ld: ./aaa/tmp/libc.a(c.o): undefined reference to symbol '__cxa_atexit@@GLIBC_2.2.5'
/usr/bin/ld: note: '__cxa_atexit@@GLIBC_2.2.5' is defined in DSO /usr/lib64/libc.so.6 so try adding it to the linker command line
/usr/lib64/libc.so.6: could not read symbols: Invalid operation
collect2: error: ld returned 1 exit status

建立動態連結檔案並編譯

編譯生成動態庫

g++ -fPIC -shared -o ./soso/libab.so a.cpp b.cpp

實際上上述過程分為編譯和連結兩步, -fPIC是編譯選項,PIC是 Position Independent Code 的縮寫,表示要生成位置無關的程式碼,這是動態庫需要的特性; -shared是連結選項,告訴g++生成動態庫而不是可執行檔案。

上述的一行命令等同於:

g++ -c -fPIC a.cpp b.cpp # 注意必須帶-fPIC,不帶的話生成的.o目標檔案是沒法用於生成.so檔案的
g++ -shared -o libab.so a.o b.o

g++ c.cpp -o main -L./soso -lab 生成main,其中-lab表示要連結libab.so
-L./soso表示搜尋要連結的庫檔案時包含./soso路徑。

如果同一目錄下同時存在同名的動態庫和靜態庫,比如 libab.solibab.a 都在當前路徑下,g++會優先連結動態庫。想連結靜態庫需要顯式指定g++ -static c.cpp -o main -L. -lab

動態連結檔案:在程式執行過程中動態呼叫庫檔案,很方便,不佔空間

動態連結檔案(.so檔案)連結,不講究順序

1、使用3個so動態連結檔案
[root@localhost test]# g++ -o main soso/liba.so soso/libb.so soso/libc.so
[root@localhost test]# g++ -o main soso/libc.so soso/libb.so soso/liba.so
[root@localhost test]# g++ -o main soso/*.so

2、使用2個so動態連結檔案
[root@localhost test]# g++ c.cpp -o main soso/libb.so 
soso/libb.so: undefined reference to `A::hello()'
collect2: error: ld returned 1 exit status
[root@localhost test]# g++ c.cpp -o main soso/libb.so soso/liba.so

3、將a.cpp和b.cpp製作成so,然後編譯c.cpp時進行連結
[root@localhost test]# g++ -fPIC -shared -o ./soso/libab.so a.cpp b.cpp
[root@localhost test]# g++ c.cpp -o main -L./soso -lab
[root@localhost test]# ./main
./main: error while loading shared libraries: libab.so: cannot open shared object file: No such file or directory
找不到libab.so,因為Linux是通過 /etc/ld.so.cache 檔案搜尋要連結的動態庫的, /etc/ld.so.cache 是 ldconfig 程式讀取 /etc/ld.so.conf 檔案生成的(/etc/ld.so.conf 中並不一定需要包含 /lib 和 /usr/lib,ldconfig程式會自動搜尋這兩個目錄)。

如果我們把 libab.so 所在的路徑新增到 /etc/ld.so.conf 中,再以root許可權執行 ldconfig 程式,更新 /etc/ld.so.cache ,main執行時,就可以找到 libab.so。

也可以直接用命令LD_LIBRARY_PATH=./soso的方式指定動態連結庫路徑。
[root@localhost test]# LD_LIBRARY_PATH=./soso ./main
C is running
B is running
hello world
B is end
C is end

4、將a.cpp,b.cpp和c.cpp製作成so,然後直接生成可執行檔案時連結
[root@localhost test]# g++ -fPIC -shared -o ./soso/libabc.so a.cpp b.cpp c.cpp
[root@localhost test]# g++ -o main -L./soso -labc
[root@localhost test]# LD_LIBRARY_PATH=./soso ./main
C is running
B is running
hello world
B is end
C is end

5、分別製作三個so,分別連結,發現不行,原因未知
[root@localhost test]# g++ -fPIC -shared -o ./soso/liba.so a.cpp
[root@localhost test]# g++ -fPIC -shared -o ./soso/libb.so b.cpp
[root@localhost test]# g++ -fPIC -shared -o ./soso/libc.so c.cpp
[root@localhost test]# g++ -o main -L./soso -la -lb -lc
/usr/bin/ld: /usr/lib/gcc/x86_64-linux/4.8.5/../../../../lib64/crt1.o: undefined reference to symbol '__libc_start_main@@GLIBC_2.2.5'
/usr/bin/ld: note: '__libc_start_main@@GLIBC_2.2.5' is defined in DSO /usr/lib64/libc.so.6 so try adding it to the linker command line
/usr/lib64/libc.so.6: could not read symbols: Invalid operation
collect2: error: ld returned 1 exit status
[root@localhost test]# g++ -o main -L./soso -lc -lb -la
/usr/bin/ld: /usr/lib/gcc/x86_64-linux/4.8.5/../../../../lib64/crt1.o: undefined reference to symbol '__libc_start_main@@GLIBC_2.2.5'
/usr/bin/ld: note: '__libc_start_main@@GLIBC_2.2.5' is defined in DSO /usr/lib64/libc.so.6 so try adding it to the linker command line
/usr/lib64/libc.so.6: could not read symbols: Invalid operation
collect2: error: ld returned 1 exit status

製作makefile

將連結2個和3個動態檔案及靜態檔案的方式做成makefile。

Makefile有三個非常有用的變數:
$@:目標檔案
$^:所有的依賴檔案
$<:第一個依賴檔案。

.PHONY: buildabcso testabcso buildabso testabso buildabca testabca buildaba testaba clean

buildabcso: libabc.so

buildabso: libab.so

buildabca: libabc.a

buildaba: libab.a

libabc.so: a.o b.o c.o
	g++ -shared -o $@ $^

libab.so: a.o b.o
	g++ -shared -o $@ $^

libabc.a: a.o b.o c.o
	ar crv $@ $^

libab.a: a.o b.o
	ar crv $@ $^

a.o: a.cpp
	g++ -c -fPIC $<

b.o: b.cpp
	g++ -c -fPIC $<

c.o: c.cpp
	g++ -c -fPIC $<

testabcso: libabc.so
	g++ -o main -L. -labc
	LD_LIBRARY_PATH=. ./main

testabso: libab.so
	g++ c.cpp -o main -L. -lab
	LD_LIBRARY_PATH=. ./main

testabca: libabc.a
	g++ -o main -L. -labc
	./main

testaba: libab.a
	g++ c.cpp -o main -L. -lab
	./main

clean:
	rm -f *.o *.so *.a main

執行make buildabcso構建生成libabc.so,執行make testabcso使用該libabc.so動態連結庫生成main可執行檔案並執行,其他build和test同理。

makefile工作原理:
1、make會在當前目錄下找名字叫“Makefile”或“makefile”的檔案。
2、如果make後有對應指令,會去.PHONY中找到對應指令;否則預設的情況下,它會找第一條指令作為目標指令,在這裡就是即buildabcso。make會把匹配到的指令作為最終的目標檔案。
3、make發現buildabcso不存在,於是需要檢查buildabcso該檔案的依賴,在這裡是libabc.so:
①、如果發現libabc.so已經存在,直接執行,由於buildabcso的行為啥都沒定義,只有個依賴字首libabc.so,所以執行buildabcso時其實啥都沒做,即:

[root@localhost test]# make buildabcso
make: Nothing to be done for `buildabcso'.

②、如果發現libabc.so不存在,或是buildabcso所依賴的libabc.so檔案修改時間要比buildabcso這個檔案新,再檢查libabc.so的依賴,也就是a.o b.o c.o,如果a.o和b.o和c.o三個檔案已存在,才能執行g++ -shared -o $@ $^生成libabc.so,如果三個.o不存在,繼續查對應依賴生成,一直這樣迭代下去。。。
4、make會一層一層去找檔案的依賴關係,直到最終編譯出第一個目標檔案,如果某個命令執行失敗,則make會停止。

相關文章