[英]linking with static library in C
嗨,我是 C 和鏈接的初學者,我正在閱讀一本關於與靜態庫鏈接有問題的書:
讓 a 和 b 表示當前目錄中的目標模塊或靜態庫,讓 a→b 表示 a 依賴於 b,因為 b 定義了一個被 a 引用的符號。 對於以下每種情況,顯示允許靜態鏈接器解析所有符號引用的最小命令行(即,具有最少數量的目標文件和庫參數的命令行):
po → libx.a → liby.a and liby.a → libx.a →po
而書中給出的答案是:
gcc p.o libx.a liby.a libx.a
我很困惑,答案不應該是:
gcc p.o libx.a liby.a libx.a p.o
否則libx.a
的未定義符號如何由po
解析?
如果你的 C 教科書沒有說清楚,作者試圖用這個練習來說明的鏈接行為不是 C 標准規定的,實際上是 GNU binutils
鏈接器ld
行為 - Linux 中的默認系統鏈接器,通常由gcc|g++|gfortran
等代表您調用 - 可能但不一定是您可能遇到的其他鏈接器的行為。
如果你准確地給了我們練習,作者可能是一個不太了解靜態鏈接的人,而不是編寫關於它的教科書的最佳人,或者可能只是沒有非常小心地表達自己。
除非我們正在鏈接一個程序,否則默認情況下鏈接器甚至不會堅持解析所有符號引用。 所以大概我們正在鏈接一個程序(不是共享庫),如果答案是:
gcc p.o libx.a liby.a libx.a
實際上是教科書所說的,那么程序就是它必須的樣子。
但是一個程序必須有一個main
函數。 main
函數在哪里,它與po
、 libx.a
和liby.a
鏈接關系是什么? 這很重要,我們沒有被告知。
所以讓我們假設p
代表program ,並且 main 函數至少在po
定義。 奇怪的是,雖然liby.a
依賴於po
,其中po
是程序的主要對象模塊,但在靜態庫的成員中定義main
函數會更奇怪。
假設這么多,這里有一些源文件:
個人電腦
#include <stdio.h>
extern void x(void);
void p(void)
{
puts(__func__);
}
int main(void)
{
x();
return 0;
}
xc
#include <stdio.h>
void x(void)
{
puts(__func__);
}
yc
#include <stdio.h>
void y(void)
{
puts(__func__);
}
呼叫中心
extern void x(void);
void callx(void)
{
x();
}
呼叫.c
extern void y(void);
void cally(void)
{
y();
}
調用程序
extern void p(void);
void callp(void)
{
p();
}
將它們全部編譯為目標文件:
$ gcc -Wall -Wextra -c p.c x.c y.c callx.c cally.c callp.c
並制作靜態庫libx.a
和liby.a
:
$ ar rcs libx.a x.o cally.o callp.o
$ ar rcs liby.a y.o callx.o
現在, po
、 libx.a
和liby.a
滿足練習的條件:
p.o → libx.a → liby.a and liby.a → libx.a →p.o
因為:
po
指但不定義x
,它在libx.a
定義。
libx.a
定義了cally
,它引用但不定義y
,它在liby.a
定義
liby.a
定義了callx
,它引用但不定義x
,它在libx.a
定義。
libx.a
定義了callp
,它引用但不定義p
,它在po
定義。
我們可以用nm
確認:
$ nm p.o
0000000000000000 r __func__.2252
U _GLOBAL_OFFSET_TABLE_
0000000000000013 T main
0000000000000000 T p
U puts
U x
po
定義p
( = T p
) 並引用x
( = U x
)
$ nm libx.a
x.o:
0000000000000000 r __func__.2250
U _GLOBAL_OFFSET_TABLE_
U puts
0000000000000000 T x
cally.o:
0000000000000000 T cally
U _GLOBAL_OFFSET_TABLE_
U y
callp.o:
0000000000000000 T callp
U _GLOBAL_OFFSET_TABLE_
U p
libx.a
定義x
( = T x
) 和引用y
( = U y
) 和引用p
( = U p
)
$ nm liby.a
y.o:
0000000000000000 r __func__.2250
U _GLOBAL_OFFSET_TABLE_
U puts
0000000000000000 T y
callx.o:
0000000000000000 T callx
U _GLOBAL_OFFSET_TABLE_
U x
liby.a
定義y
( = T y
) 並引用x
( = U x
)
現在教科書的聯動肯定成功了:
$ gcc p.o libx.a liby.a libx.a
$ ./a.out
x
但它是最短的鏈接嗎? 不,這是:
$ gcc p.o libx.a
$ ./a.out
x
為什么? 讓我們重新運行與診斷的鏈接,以顯示實際鏈接了哪些目標文件:
$ gcc p.o libx.a -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
p.o
(libx.a)x.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
他們是:
p.o
(libx.a)x.o
po
首先鏈接到程序中,因為輸入.o
文件總是無條件鏈接的。
然后是libx.a
。 閱讀靜態庫以了解鏈接器如何處理它。 鏈接po
,它只有一個未解析的引用 - 對x
的引用。 它檢查libx.a
尋找定義x
的目標文件。 它找到(libx.a)xo
。 它從libx.a
提取xo
並鏈接它,然后就完成了。 1
所有涉及liby.a
的依賴關系:-
(libx.a)cally.o
依賴於(liby.a)yo
(liby.a)callx.o
依賴於(libx.a)xo
是無關的聯動,因為聯動不需要任何目標文件liby.a
。
鑒於作者所說的是正確答案,我們可以對他們試圖陳述的練習進行逆向工程。 就是這個:
定義main
的對象模塊po
引用了它沒有定義的符號x
,而x
在靜態庫libxz.a
成員xo
中定義
(libxz.a)xo
指的是它沒有定義的符號y
,而y
是在靜態庫liby.a
成員yo
中定義的
(liby.a)yo
指的是它沒有定義的符號z
,而z
在libxz.a
成員zo
中libxz.a
。
(liby.a)yo
指的是它沒有定義的符號p
,而p
是在po
定義的
使用po
、 libxz.a
、 liby.a
的最小鏈接命令是什么?
新的源文件:
個人電腦
Stays as before.
xc
#include <stdio.h>
extern void y();
void cally(void)
{
y();
}
void x(void)
{
puts(__func__);
}
yc
#include <stdio.h>
extern void z(void);
extern void p(void);
void callz(void)
{
z();
}
void callp(void)
{
p();
}
void y(void)
{
puts(__func__);
}
零點
#include <stdio.h>
void z(void)
{
puts(__func__);
}
新的靜態庫:
$ ar rcs libxz.a x.o z.o
$ ar rcs liby.a y.o
現在鏈接:
$ gcc p.o libxz.a
libxz.a(x.o): In function `cally':
x.c:(.text+0xa): undefined reference to `y'
collect2: error: ld returned 1 exit status
失敗,就像:
$ gcc p.o libxz.a liby.a
liby.a(y.o): In function `callz':
y.c:(.text+0x5): undefined reference to `z'
collect2: error: ld returned 1 exit status
和:
$ gcc p.o liby.a libxz.a
libxz.a(x.o): In function `cally':
x.c:(.text+0xa): undefined reference to `y'
collect2: error: ld returned 1 exit status
和(您自己的選擇):
$ gcc p.o liby.a libxz.a p.o
p.o: In function `p':
p.c:(.text+0x0): multiple definition of `p'
p.o:p.c:(.text+0x0): first defined here
p.o: In function `main':
p.c:(.text+0x13): multiple definition of `main'
p.o:p.c:(.text+0x13): first defined here
libxz.a(x.o): In function `cally':
x.c:(.text+0xa): undefined reference to `y'
collect2: error: ld returned 1 exit status
因未定義引用錯誤和多重定義錯誤而失敗。
但是教科書的答案是:
$ gcc p.o libxz.a liby.a libxz.a
$ ./a.out
x
現在是對的。
作者試圖描述一個程序鏈接中兩個靜態庫之間的相互依賴,但發現這種相互依賴只有在鏈接需要每個庫中至少有一個引用某個符號的目標文件時才能存在。由另一個庫中的目標文件定義。
從修正后的練習中要吸取的教訓是:
出現在鏈接器輸入中的目標文件foo.o
永遠不需要出現多次,因為它將被無條件鏈接,並且當它被鏈接時,它提供的任何符號s
的定義將用於解析對s
所有引用任何其他鏈接器輸入的累積。 如果foo.o
被輸入兩次,你只能得到s
多重定義的錯誤。
但是如果鏈接中的靜態庫之間存在相互依賴關系,則可以通過兩次輸入其中一個庫來解決。 因為目標文件是從靜態庫中提取並鏈接的,當且僅當需要該目標文件來定義鏈接器在輸入庫時試圖定義的未解析符號引用時。 所以在更正的例子中:
po
是輸入並無條件鏈接。x
成為未解析的引用。libxz.a
是輸入。x
的定義可以在(libxz.a)xo
。(libxz.a)xo
被提取和鏈接。x
已解決。(libxz.a)xo
指的是y
。y
成為未解析的引用。liby.a
是輸入。y
的定義可以在(liby.a)yo
。(liby.a)yo
被提取和鏈接。y
已解決。(liby.a)yo
指的是z
。z
成為未解析的參考。libxz.a
。libxz.a(zo)
可以找到z
的定義libxz.a(zo)
被提取和鏈接。z
已解決。-trace
輸出所示,嚴格來說,直到(libx.a)xo
之后的所有樣板也被鏈接后,鏈接才完成,但對於每個 C 程序鏈接,它都是相同的樣板。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.