提问人:AndyB 提问时间:8/2/2018 最后编辑:AndyB 更新时间:2/14/2020 访问量:43846
__attribute__((弱))和静态库
__attribute__((weak)) and static libraries
问:
我想在我的代码中引入一个弱符号,但是,当使用 *.a 文件时,我无法理解它的行为。
这是我最小的例子:
文件A.H:
void foo() __attribute__((weak));
文件A.C:
#include "a.h"
#include <stdio.h>
void foo() { printf("%s\n", __FILE__); }
文件b.c:
#include <stdio.h>
void foo() { printf("%s\n", __FILE__); }
文件主.cpp:
#include "a.h"
#include <stdio.h>
int main() { if (foo) foo(); else printf("no foo\n"); }
现在,根据我使用的是 *.o 文件(和)还是 *.a 文件(和),输出是不同的:make -c a.c
make -c b.c
ar cr a.o
ar cr b.o
1) 打印 BC
2) 打印 BC
3) 打印无 FOO 4) 打印无 FOO
g++ main.cpp a.o b.o
g++ main.cpp b.o a.o
g++ main.cpp a.a b.a
g++ main.cpp b.a a.a
1), 2) 工作正常,但 3), 4) 的输出似乎有点出乎意料。
我拼命地试图让这个例子与档案一起使用,所以我做了一些改动:
文件A.H:
void foo();
文件A.C:
#include "a.h"
#include <stdio.h>
void __attribute__((weak)) foo() { printf("%s\n", __FILE__); }
修改后:
1) 打印 A.C 2) 打印 B.C
g++ main.cpp a.a b.a
g++ main.cpp b.a a.a
所以它的效果会更好一些。运行节目后,没有违反 ODR。但是,我不知道这是否是弱属性的正确用法。根据 gcc 文档:nm a.a
W _Z3foov
weak 属性导致声明作为弱符号而不是全局符号发出。这主要用于定义可在用户代码中重写的库函数,尽管它也可以与非函数声明一起使用。ELF 目标支持弱符号,使用 GNU 汇编器和链接器时也支持 a.out 目标。
然而,我在函数定义上使用弱属性,而不是声明。
那么问题来了,为什么 weak 不适用于 *.a 文件?在定义上使用弱属性而不是声明是否正确?
更新
我突然意识到,与 foo() 方法定义一起使用的弱属性对符号分辨率没有影响。如果没有该属性,最终二进制文件会生成相同的结果:
1) 打印 A.C 2) 打印 B.C
g++ main.cpp a.a b.a
g++ main.cpp b.a a.a
因此,只需使用符号的第一个定义,这与默认的 gcc 行为一致。即使显示发出了弱符号,它似乎也不会影响静态链接。nm a.a
是否可以将弱属性与静态链接一起使用?
我要解决的问题的描述
我有一个被 >20 个客户端使用的库,我们称之为库 A。我还提供了一个库 B,其中包含 A 的测试实用程序。 不知何故,我需要知道库 A 是在测试模式下使用的,因此最简单的解决方案似乎是在与 B 链接期间替换符号(因为客户端已经与 B 链接)。
我知道这个问题有更干净的解决方案,但是我绝对不能影响客户的代码或他们的构建脚本(添加参数来指示测试 A 或某些 DEFINE 进行编译是没有选择的)。
答:
我发现这是最好的解释:
如果链接器在搜索所有输入对象后无法解析该引用,则链接器将仅搜索库以解析该引用。如果需要,将根据库在链接器命令行上的位置从左到右搜索库。库中的对象将按其存档顺序进行搜索。一旦 armlink 找到引用的符号匹配项,搜索就完成了,即使它与弱定义匹配也是如此。
ELF ABI 第 4.6.1.2 节说:“
弱定义不会改变从库中选择目标文件的规则。但是,如果链接集同时包含弱定义和非弱定义,则将始终使用非弱定义。
“链接集”是链接器加载的对象集。它不包括库中不需要的对象。
因此,不建议将两个对象存档到库或单独的库中,其中一个对象包含给定符号的弱定义,另一个对象包含该符号的非弱定义。
请注意以下事项。基本上重命名了 .mv a.c definition.c
mv b.c noweak.c
mv second_a.c declaration.c
> for i in Makefile *.c; do echo "cat $i <<EOF"; cat $i; echo EOF; done
cat Makefile <<EOF
tgt=
tgt+=only_weak_1.out only_weak_2.out
tgt+=definition.out declaration.out noweak.out
tgt+=definition_static.out declaration_static.out noweak_static.out
tgt+=1.out 2.out 3.out 4.out
tgt+=5.out 6.out 7.out 8.out
tgt+=10.out 11.out 12.out
tgt+=13.out
tgt+=14.out
only_weak_1_obj= definition.o declaration.o
only_weak_2_obj= declaration.o definition.o
definition_obj= definition.o
declaration_obj= declaration.o
noweak_obj= noweak.o
definition_static_obj= definition.a
declaration_static_obj= declaration.a
noweak_static_obj= noweak.a
1_obj= declaration.o noweak.o
2_obj= noweak.o declaration.o
3_obj= declaration.a noweak.a
4_obj= noweak.a declaration.a
5_obj= definition.o noweak.o
6_obj= noweak.o definition.o
7_obj= definition.a noweak.a
8_obj= noweak.a definition.a
10_obj= noweak.a definition.a declaration.a
11_obj= definition.a declaration.a noweak.a
12_obj= declaration.a definition.a noweak.a
13_obj= all.a
14_obj= all.o
.PRECIOUS: % %.o %.c %.a
def: run
.PHONY: run
run: $(tgt)
{ $(foreach a,$^,echo "$($(a:.out=)_obj)#->#$(a)#:#$$(./$(a))";) } | { echo; column -t -s'#' -N 'objects, ,executable, ,output' -o' '; echo; }
.SECONDEXPANSION:
%.out: main.o $$(%_obj)
$(CC) -o $@ $^
%.o: %.c
$(CC) -c -o $@ $^
%.a: %.o
ar cr $@ $^
all.a: declaration.o definition.o noweak.o
ar cr $@ $^
all.o: declaration.o definition.o noweak.o
$(LD) -i -o $@ $^
clean:
rm -fv *.o *.a *.out
EOF
cat declaration.c <<EOF
#include <stdio.h>
__attribute__((__weak__)) void foo();
void foo() { printf("%s\n", __FILE__); }
EOF
cat definition.c <<EOF
#include <stdio.h>
__attribute__((__weak__)) void foo() { printf("%s\n", __FILE__); }
EOF
cat main.c <<EOF
#include <stdio.h>
void foo();
int main() {
if (foo) foo(); else printf("no foo\n");
return 0;
}
EOF
cat noweak.c <<EOF
#include <stdio.h>
void foo() { printf("%s\n", __FILE__); }
EOF
> make
cc -c -o definition.o definition.c
cc -c -o declaration.o declaration.c
cc -c -o main.o main.c
cc -o only_weak_1.out main.o definition.o declaration.o
cc -o only_weak_2.out main.o declaration.o definition.o
cc -o definition.out main.o definition.o
cc -o declaration.out main.o declaration.o
cc -c -o noweak.o noweak.c
cc -o noweak.out main.o noweak.o
ar cr definition.a definition.o
cc -o definition_static.out main.o definition.a
ar cr declaration.a declaration.o
cc -o declaration_static.out main.o declaration.a
ar cr noweak.a noweak.o
cc -o noweak_static.out main.o noweak.a
cc -o 1.out main.o declaration.o noweak.o
cc -o 2.out main.o noweak.o declaration.o
cc -o 3.out main.o declaration.a noweak.a
cc -o 4.out main.o noweak.a declaration.a
cc -o 5.out main.o definition.o noweak.o
cc -o 6.out main.o noweak.o definition.o
cc -o 7.out main.o definition.a noweak.a
cc -o 8.out main.o noweak.a definition.a
cc -o 10.out main.o noweak.a definition.a declaration.a
cc -o 11.out main.o definition.a declaration.a noweak.a
cc -o 12.out main.o declaration.a definition.a noweak.a
ar cr all.a declaration.o definition.o noweak.o
cc -o 13.out main.o all.a
ld -i -o all.o declaration.o definition.o noweak.o
cc -o 14.out main.o all.o
{ echo "definition.o declaration.o#->#only_weak_1.out#:#$(./only_weak_1.out)"; echo "declaration.o definition.o#->#only_weak_2.out#:#$(./only_weak_2.out)"; echo "definition.o#->#definition.out#:#$(./definition.out)"; echo "declaration.o#->#declaration.out#:#$(./declaration.out)"; echo "noweak.o#->#noweak.out#:#$(./noweak.out)"; echo "definition.a#->#definition_static.out#:#$(./definition_static.out)"; echo "declaration.a#->#declaration_static.out#:#$(./declaration_static.out)"; echo "noweak.a#->#noweak_static.out#:#$(./noweak_static.out)"; echo "declaration.o noweak.o#->#1.out#:#$(./1.out)"; echo "noweak.o declaration.o#->#2.out#:#$(./2.out)"; echo "declaration.a noweak.a#->#3.out#:#$(./3.out)"; echo "noweak.a declaration.a#->#4.out#:#$(./4.out)"; echo "definition.o noweak.o#->#5.out#:#$(./5.out)"; echo "noweak.o definition.o#->#6.out#:#$(./6.out)"; echo "definition.a noweak.a#->#7.out#:#$(./7.out)"; echo "noweak.a definition.a#->#8.out#:#$(./8.out)"; echo "noweak.a definition.a declaration.a#->#10.out#:#$(./10.out)"; echo "definition.a declaration.a noweak.a#->#11.out#:#$(./11.out)"; echo "declaration.a definition.a noweak.a#->#12.out#:#$(./12.out)"; echo "all.a#->#13.out#:#$(./13.out)"; echo "all.o#->#14.out#:#$(./14.out)"; } | { echo; column -t -s'#' -N 'objects, ,executable, ,output' -o' '; echo; }
objects executable output
definition.o declaration.o -> only_weak_1.out : definition.c
declaration.o definition.o -> only_weak_2.out : declaration.c
definition.o -> definition.out : definition.c
declaration.o -> declaration.out : declaration.c
noweak.o -> noweak.out : noweak.c
definition.a -> definition_static.out : definition.c
declaration.a -> declaration_static.out : declaration.c
noweak.a -> noweak_static.out : noweak.c
declaration.o noweak.o -> 1.out : noweak.c
noweak.o declaration.o -> 2.out : noweak.c
declaration.a noweak.a -> 3.out : declaration.c
noweak.a declaration.a -> 4.out : noweak.c
definition.o noweak.o -> 5.out : noweak.c
noweak.o definition.o -> 6.out : noweak.c
definition.a noweak.a -> 7.out : definition.c
noweak.a definition.a -> 8.out : noweak.c
noweak.a definition.a declaration.a -> 10.out : noweak.c
definition.a declaration.a noweak.a -> 11.out : definition.c
declaration.a definition.a noweak.a -> 12.out : declaration.c
all.a -> 13.out : declaration.c
all.o -> 14.out : noweak.c
如果仅使用弱符号(大小写 only_weak_1 和 only_weak_2),则使用第一个定义。
如果仅是静态库(情况 3、4、7、8、10、11、12、13),则使用第一个定义。
如果仅使用目标文件(情况 1、2、5、6、14),则省略弱符号,仅使用 noweak 中的符号。
从我提供的链接:
有多种方法可以保证 armlink 选择给定符号的非弱版本:
- 不要存档此类对象 - 在存档之前,确保弱符号和非弱符号包含在同一个对象
中
- 使用部分链接作为替代方法。
评论
objcopy -Nfoo noweak.a; gcc -o main.out noweak.a declaration.a main.c
为了解释这里发生了什么,让我们先谈谈你的原始源文件,用
A.H (1):
void foo() __attribute__((weak));
和:
交流 (1):
#include "a.h"
#include <stdio.h>
void foo() { printf("%s\n", __FILE__); }
示例代码中 和 文件的混合与
问题,并且所有代码都是 C,所以我们会说是
使用以下方法进行所有编译和链接:.c
.cpp
main.cpp
main.c
gcc
$ gcc -Wall -c main.c a.c b.c
ar rcs a.a a.o
ar rcs b.a b.o
首先,让我们回顾一下弱声明符号之间的差异,例如 你:
void foo() __attribute__((weak));
以及一个强烈声明的符号,例如
void foo();
这是默认值:
当在程序中链接对 的弱引用(即对弱声明的引用)时, 链接器不需要在链接中找到任何位置的定义:它可能保留 定义。如果在程序中链接了强引用, 链接器需要找到 的定义。
foo
foo
foo
foo
foo
一个联系最多可以包含一个强有力的定义(即一个定义 强烈地宣布了这一点)。否则,将导致多重定义错误。 但它可能包含多个无误的弱定义。
foo
foo
foo
如果链接包含一个或多个弱定义和强定义 定义,则链接器选择强定义并忽略弱定义 的。
foo
如果一个链接只包含一个弱定义,而没有强定义 定义,链接器不可避免地使用一个弱定义。
foo
如果链接包含多个弱定义和无强定义 definition,则链接器任意选择一个弱定义。
foo
接下来,让我们回顾一下在链接中输入对象文件之间的区别 并输入静态库。
静态库只是我们可以提供给的目标文件的存档
从中选择进行链接所需的链接器。ar
当将对象文件输入到链接时,链接器会无条件地链接它 添加到输出文件中。
当静态库输入到链接时,链接器会检查存档 在其中查找任何对象文件,这些文件为未解析的符号引用提供所需的定义 从已链接的输入文件中累积的。如果找到任何此类对象文件 在存档中,它提取它们并将它们链接到输出文件中,就像 如果它们是单独命名的输入文件,并且根本没有提及静态库。
考虑到这些观察结果,请考虑 compile-and-link 命令:
gcc main.c a.o b.o
在幕后,它必须分解为编译步骤和链接
步骤,就像你跑了:gcc
gcc -c main.c # compile
gcc main.o a.o b.o # link
所有三个对象文件都无条件地链接到(默认)程序中。 包含一个
的弱定义,正如我们所看到的:./a.out
a.o
foo
$ nm --defined a.o
0000000000000000 W foo
鉴于包含一个强有力的定义:b.o
$ nm --defined b.o
0000000000000000 T foo
链接器将找到这两个定义,并从中选择强定义,尽我们所能
另请参阅:b.o
$ gcc main.o a.o b.o -Wl,-trace-symbol=foo
main.o: reference to foo
a.o: definition of foo
b.o: definition of foo
$ ./a.out
b.c
颠倒 和 的链接顺序不会有什么区别:有
仍然正好是 的一个强定义 ,中的那个。a.o
b.o
foo
b.o
相比之下,请考虑 compile-and-link 命令:
gcc main.cpp a.a b.a
具体分解为:
gcc -c main.cpp # compile
gcc main.o a.a b.a # link
在这里,只有无条件链接。这放置了一个未定义的弱引用
要进入联动:main.o
foo
$ nm --undefined main.o
w foo
U _GLOBAL_OFFSET_TABLE_
U puts
这种薄弱的提及不需要定义。所以链接器将
不要尝试在 OR 和
将在程序中将其保留为未定义状态,正如我们所看到的:foo
a.a
b.a
$ gcc main.o a.a b.a -Wl,-trace-symbol=foo
main.o: reference to foo
$ nm --undefined a.out
w __cxa_finalize@@GLIBC_2.2.5
w foo
w __gmon_start__
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U __libc_start_main@@GLIBC_2.2.5
U puts@@GLIBC_2.2.5
因此:
$ ./a.out
no foo
同样,如果颠倒 和 , 的联动顺序也没关系,
但这一次是因为他们俩都没有对这种联系做出任何贡献。a.a
b.a
现在让我们转向您通过更改和以下方式发现的不同行为:a.h
a.c
A.H (2):
void foo();
交流 (2):
#include "a.h"
#include <stdio.h>
void __attribute__((weak)) foo() { printf("%s\n", __FILE__); }
再来一次:
$ gcc -Wall -c main.c a.c b.c
main.c: In function ‘main’:
main.c:4:18: warning: the address of ‘foo’ will always evaluate as ‘true’ [-Waddress]
int main() { if (foo) foo(); else printf("no foo\n"); }
看到那个警告了吗? 现在包含对以下命令的强烈声明引用:main.o
foo
$ nm --undefined main.o
U foo
U _GLOBAL_OFFSET_TABLE_
因此,代码(链接时)必须具有 的非 null 地址。进行中:foo
$ ar rcs a.a a.o
$ ar rcs b.a b.o
然后尝试链接:
$ gcc main.o a.o b.o
$ ./a.out
b.c
在反转目标文件的情况下:
$ gcc main.o b.o a.o
$ ./a.out
b.c
和以前一样,顺序没有区别。所有目标文件都已链接。 提供
,的强定义提供了一个弱定义,所以赢了。b.o
foo
a.o
b.o
接下来尝试联动:
$ gcc main.o a.a b.a
$ ./a.out
a.c
随着库的顺序颠倒:
$ gcc main.o b.a a.a
$ ./a.out
b.c
这确实有所作为。为什么?让我们重做与诊断的联系:
$ gcc main.o a.a b.a -Wl,-trace,-trace-symbol=foo
/usr/bin/x86_64-linux-gnu-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
main.o
(a.a)a.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
main.o: reference to foo
a.a(a.o): definition of foo
忽略默认库,我们唯一得到的对象文件 链接有:
main.o
(a.a)a.o
而 的定义取自 的档案成员:foo
a.o
a.a
a.a(a.o): definition of foo
颠倒库顺序:
$ gcc main.o b.a a.a -Wl,-trace,-trace-symbol=foo
/usr/bin/x86_64-linux-gnu-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
main.o
(b.a)b.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
main.o: reference to foo
b.a(b.o): definition of foo
这次链接的对象文件是:
main.o
(b.a)b.o
的定义取自:foo
b.o
b.a
b.a(b.o): definition of foo
在第一个链接中,链接器有一个未解析的强引用,当它到达时,它需要对该引用进行定义。所以它
在存档中查找提供定义的对象文件,
并发现.这个定义很弱,但这并不重要。不
已经看到了强有力的定义。 摘自并链接,
因此,对 的提及得到了解决。接下来到达了哪里,哪里
如果链接器仍然需要,则会在 中找到强有力的定义
并寻找它。但它不再需要一个,也没有看。联动:foo
a.a
a.o
a.o
a.a
foo
b.a
foo
b.o
gcc main.o a.a b.a
与以下完全相同:
gcc main.o a.o
同样,链接:
$ gcc main.o b.a a.a
与以下完全相同:
$ gcc main.o b.o
你真正的问题...
...出现在您对帖子的评论之一中:
我想在与测试框架链接时覆盖原始函数实现。
你想链接一个程序,输入一些静态库,该库有一些定义符号的成员,你想敲掉
该定义并链接了在其他对象中定义的不同定义
文件。lib1.a
file1.o
foo
foo
file2.o
__attribute__((weak))
不适用于该问题。解决方案是更多
初等。您只需确保在输入之前(以及在提供 的 定义的任何其他输入之前)输入到链接。
然后,链接器将解析对 的引用,并不会尝试查找任何其他定义
定义,当它达到 .链接器根本不会消耗。它可能不存在。file2.o
lib1.a
foo
foo
file2.o
lib1.a
lib1.a(file1.o)
如果你放了另一个静态库怎么办?然后输入 before 将完成链接 before 的工作,并解析为 中的定义。file2.o
lib2.a
lib2.a
lib1.a
lib2.a(file2.o)
lib1.a
foo
file2.o
同样,当然,成员提供的每个定义都将链接在
优先于 中提供的相同符号的定义。如果那不是
你想要,然后不喜欢:链接本身。lib2.a
lib1.a
lib2.a
file2.o
最后
是否可以将弱属性与静态链接一起使用?
当然。下面是一个第一性原理用例:
foo.h (1)
#ifndef FOO_H
#define FOO_H
int __attribute__((weak)) foo(int i)
{
return i != 0;
}
#endif
AA.C公司
#include "foo.h"
int a(void)
{
return foo(0);
}
bb.c的
#include "foo.h"
int b(void)
{
return foo(42);
}
prog.c网站
#include <stdio.h>
extern int a(void);
extern int b(void);
int main(void)
{
puts(a() ? "true" : "false");
puts(b() ? "true" : "false");
return 0;
}
编译所有源文件,为每个函数请求单独的 ELF 部分:
$ gcc -Wall -ffunction-sections -c prog.c aa.c bb.c
请注意,的弱定义通过 和 编译为 ,正如我们所看到的:foo
foo.h
aa.o
bb.o
$ nm --defined aa.o
0000000000000000 T a
0000000000000000 W foo
$ nm --defined bb.o
0000000000000000 T b
0000000000000000 W foo
现在,从所有目标文件中链接一个程序,请求链接器 丢弃未使用的部分(并给我们地图文件和一些诊断):
$ gcc prog.o aa.o bb.o -Wl,--gc-sections,-Map=mapfile,-trace,-trace-symbol=foo
/usr/bin/x86_64-linux-gnu-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
prog.o
aa.o
bb.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
aa.o: definition of foo
这种联系与以下没有什么不同:
$ ar rcs libaabb.a aa.o bb.o
$ gcc prog.o libaabb.a
尽管两者都加载了,并且每个都包含
一个定义,不会产生多定义错误,因为每个定义
很弱。 之前加载过,并且 的定义是从 链接的。aa.o
bb.o
foo
aa.o
bb.o
foo
aa.o
那么in的定义怎么了呢?地图文件显示:foo
bb.o
地图文件 (1)
...
...
Discarded input sections
...
...
.text.foo 0x0000000000000000 0x13 bb.o
...
...
链接器放弃了包含定义的函数部分
在bb.o
让我们反转 和 的链接顺序:aa.o
bb.o
$ gcc prog.o bb.o aa.o -Wl,--gc-sections,-Map=mapfile,-trace,-trace-symbol=foo
...
prog.o
bb.o
aa.o
...
bb.o: definition of foo
而现在,恰恰相反的事情发生了。 在 之前加载。这
的定义是从和链接的:bb.o
aa.o
foo
bb.o
地图文件 (2)
...
...
Discarded input sections
...
...
.text.foo 0x0000000000000000 0x13 aa.o
...
...
的定义被抛弃了。aa.o
在这里,您可以看到链接器如何任意选择多个中的一个 在缺乏强定义的情况下,符号的弱定义。它只是 选择你给它的第一个,忽略其余的。
我们刚刚在这里所做的实际上是 GCC C++ 编译器为我们所做的,当我们 定义全局内联函数。重写:
foo.h (2)
#ifndef FOO_H
#define FOO_H
inline int foo(int i)
{
return i != 0;
}
#endif
重命名源文件 -> ;编译和链接:*.c
*.cpp
$ g++ -Wall -c prog.cpp aa.cpp bb.cpp
现在,每个 和 中都有一个弱定义(C++ mangled):foo
aa.o
bb.o
$ nm --defined aa.o bb.o
aa.o:
0000000000000000 T _Z1av
0000000000000000 W _Z3fooi
bb.o:
0000000000000000 T _Z1bv
0000000000000000 W _Z3fooi
该链接使用它找到的第一个定义:
$ g++ prog.o aa.o bb.o -Wl,-Map=mapfile,-trace,-trace-symbol=_Z3fooi
...
prog.o
aa.o
bb.o
...
aa.o: definition of _Z3fooi
bb.o: reference to _Z3fooi
并扔掉另一个:
地图文件 (3)
...
...
Discarded input sections
...
...
.text._Z3fooi 0x0000000000000000 0x13 bb.o
...
...
您可能知道,C++ 函数模板的每个实例化 全局作用域(或类模板成员函数的实例化)为 内联全局函数。再次重写:
#ifndef FOO_H
#define FOO_H
template<typename T>
T foo(T i)
{
return i != 0;
}
#endif
重新 编译:
$ g++ -Wall -c prog.cpp aa.cpp bb.cpp
再:
$ nm --defined aa.o bb.o
aa.o:
0000000000000000 T _Z1av
0000000000000000 W _Z3fooIiET_S0_
bb.o:
0000000000000000 T _Z1bv
0000000000000000 W _Z3fooIiET_S0_
每个 和 都有一个弱定义:aa.o
bb.o
$ c++filt _Z3fooIiET_S0_
int foo<int>(int)
现在,这种联系行为已经很熟悉了。一种方式:
$ g++ prog.o aa.o bb.o -Wl,-Map=mapfile,-trace,-trace-symbol=_Z3fooIiET_S0_
...
prog.o
aa.o
bb.o
...
aa.o: definition of _Z3fooIiET_S0_
bb.o: reference to _Z3fooIiET_S0_
另一种方式:
$ g++ prog.o bb.o aa.o -Wl,-Map=mapfile,-trace,-trace-symbol=_Z3fooIiET_S0_
...
prog.o
bb.o
aa.o
...
bb.o: definition of _Z3fooIiET_S0_
aa.o: reference to _Z3fooIiET_S0_
我们的程序的行为不会因重写而改变:
$ ./a.out
false
true
那么在ELF对象的联动中,弱属性对符号的应用—— 无论是静态还是动态 - 启用 C++ 模板的 GCC 实现 用于 GNU 链接器。你可以公平地说,它支持现代 C++ 的 GCC 实现。
评论
inline
gcc
.c
extern inline void f();
gcc
-fno-common
inline
extern inline
评论
objcopy -N foo a.o
no foo
void (*foo)() = 0;
make -c
cc
foo
_Z3foov
no foo
g++ -c a.c
ar cr a.a a.o
g++ -c b.c
ar cr b.a b.o
g++ main.cpp a.a b.a
void (*foo)() = 0;