我有下面的树:
.
├── func1.c
├── func2.c
├── main.c
├── Makefile
├── override.c
└── weak.h
- main.c 调用 func1()。
- func1() 调用 func2()。
- weak.h 将 func2() 声明为弱。
- override.c 提供了 func2() 的覆盖版本。
函数1.c
#include <stdio.h>
void func2(void);
void func1 (void)
{
func2();
}
函数2.c
#include <stdio.h>
void func2 (void)
{
printf("in original func2()\n");
}
主程序
#include <stdio.h>
void func1();
void func2();
void main()
{
func1();
}
覆盖.c
#include <stdio.h>
void func2 (void)
{
printf("in override func2()\n");
}
弱.h
__attribute__((weak))
void func2 (void); // <==== weak attribute on declaration
生成文件
ALL:
rm -f *.a *.o
gcc -c override.c -o override.o
gcc -c func1.c -o func1.o -include weak.h # weak.h is used to tell func1.c that func2() is weak
gcc -c func2.c -o func2.o
ar cr all_weak.a func1.o func2.o
gcc main.c all_weak.a override.o -o main
所有这些都运行良好,如下所示:
in override func2()
func2()
但是,如果我删除from的覆盖版本,override.c
如下所示:
#include <stdio.h>
// void func2 (void)
// {
// printf("in override func2()\n");
// }
构建通过,但最终的二进制文件在运行时给出以下错误:
Segmentation fault (core dumped)
而在 的符号表中./main
,func2() 是一个未解析的弱符号。
000000000000065b T func1
w func2 <=== func2 is a weak symbol with no default implementation
为什么没有回到func2()
原来的样子func2.c
?
毕竟all_weak.a
已经包含了一个实现func2.o
:
func1.o:
0000000000000000 T func1
w func2 <=== func2 is [w]eak with no implementation
U _GLOBAL_OFFSET_TABLE_
func2.o:
0000000000000000 T func2 <=========== HERE! a strong symbol!
U _GLOBAL_OFFSET_TABLE_
U puts
加 1
看来翻译单元的安排也影响了功能的回退weak
。
如果我将实现放入如下func2()
相同的文件/翻译单元func1()
中,则可以退回到原始文件func2()
。
函数1.c
#include <stdio.h>
void func2 (void)
{
printf("in original func2()\n");
}
void func1 (void)
{
func2();
}
的符号all_weak.a
是:
func1.o:
0000000000000013 T func1
0000000000000000 W func2 <==== func2 is still [W]eak but has default imeplementation
U _GLOBAL_OFFSET_TABLE_
U puts
func2()
如果没有提供覆盖,代码可以正确回退到原始代码。
该链接还提到要使用 GCCalias
属性,还必须考虑翻译单元的排列。
alias (“target”) alias 属性导致声明作为另一个符号的别名发出,必须指定。例如,
void __f () { /* 做某事。*/; } void f()属性((weak, alias ("__f"))); 将 f 定义为 __f 的弱别名。在 C++ 中,必须使用目标的错位名称。如果 __f 未在同一个翻译单元中定义,则会出错。
根据维基百科:
nm 命令识别目标文件、库和可执行文件中的弱符号。在 Linux 上,如果弱默认定义可用,则弱函数符号标记为“ W ”,如果不可用,则标记为“ w ”。
添加 2 - 2021 年 8 月 7 日下午 7:54
(非常感谢@n。1.8e9-where's-my-share m。)
我试过这些:
在 func2.c
__attribute__((weak))
的func2()
定义中添加。-include weak.h
从 Makefile 中删除。
现在这些文件看起来像这样:
函数2.c
#include <stdio.h>
__attribute__((weak))
void func2 (void)
{
printf("in original func2()\n");
}
生成文件:
ALL:
rm -f *.a *.o
gcc -c override.c -o override.o
gcc -c func1.c -o func1.o
gcc -c func2.c -o func2.o
ar cr all_weak.a func1.o func2.o
gcc main.c all_weak.a -o main_original # <=== no override.o
gcc main.c all_weak.a override.o -o main_override # <=== override.o
输出是这样的:
xxx@xxx-host:~/weak_fallback$ ./main_original
in original func2() <===== successful fall back
xxx@xxx-host:~/weak_fallback$ ./main_override
in override func2() <===== successful override
所以,结论是:
如果弱函数声明(就像我在 中所做的那样
weak.h
),它本质上是告诉链接器不要解析它。如果弱函数定义(就像我在 中所做的那样
func2.c
),它本质上是告诉链接器在没有找到强版本时将其用作后备。如果弱函数声明,您最好在
.o
文件中向链接器提供覆盖版本(就像我在 中所做的那样override.o
)。在这种情况下,链接器似乎仍然愿意解析.o
文件。当您无法修改源但仍想覆盖某些功能时,就会出现这种情况。
以及这里的一些引文:
如果在搜索所有输入对象后无法解析引用,则链接器将仅搜索库以解析引用. 如果需要,将根据它们在链接器命令行上的位置从左到右搜索这些库。库中的对象将按它们的归档顺序进行搜索。一旦 armlink 找到与引用匹配的符号,搜索就完成了,即使它与弱定义匹配。ELF ABI 第 4.6.1.2 节说:“弱定义不会改变从库中选择目标文件的规则。但是,如果链接集同时包含弱定义和非弱定义,则非弱定义将永远被使用。” “链接集”是链接器已加载的对象集。它不包括不需要的库中的对象。因此,不建议将其中一个包含给定符号的弱定义而另一个包含该符号的非弱定义的两个对象归档到一个库或单独的库中。
2021 年 8 月 8 日上午 8 点 47 分添加
正如@n.1.8e9-where's-my-sharem 评论的:
评论1:
不是定义的符号上的“弱”表示“在链接时不解析此符号”。链接器高兴地服从。
评论 2:
“在一个不是定义的符号上”是错误的,应该读作“在一个未定义的符号上”。
我认为“在一个未定义的符号上”,他的意思是“当前翻译单元中的一个未定义的符号”。就我而言,当我:
func2()
在单独的func2.c文件中定义- 并编译func1.c
weak.h
这些本质上告诉链接器不要解析func2()
翻译单元func1.c中的消耗。但似乎这个“不”只适用于.a
文件。如果我链接.o
文件之外的另一个文件.a
,链接器仍然愿意解析func2()
. 或者,如果func1.cfunc2()
中也定义了,链接器也会解析它。很微妙!
(到目前为止,所有这些结论都是基于我的实验结果。总结这些很微妙。如果有人能找到一些权威来源,请随时评论或回复。谢谢!)
(感谢n. 1.8e9-where's-my-share m.的评论。)
和一个相关的线程:
事后诸葛亮 - 2021 年 8 月 8 日晚上 9:55
这些微妙的行为背后没有火箭科学。这仅取决于链接器的实现方式。有时文件是模糊的。你必须尝试并处理它。(如果所有这些背后有什么大想法,请纠正我,我将不胜感激。)