为什么在相同的 .a 文件中定义的弱符号不同 .o 文件中不用作回退



>我在下面有树:

.
├── 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();
}

func2.c

#include <stdio.h>
void func2 (void)
{
printf("in original func2()n");
}

主.c

#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()

但是,如果我从override.c中删除func2()的覆盖版本,如下所示:

#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.cfunc2()毕竟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 () {/* Do something. */; } void f ()属性((弱, 别名("__f")));将 f 定义为 __f 的弱别名。在C++, 必须使用目标的损坏名称。如果不是,则__f则为错误 在同一翻译单元中定义。

根据维基百科:

nm 命令标识对象文件、库和 可执行文件。在 Linux 上,弱函数符号标有">W",如果 弱默认定义可用,如果不是,则使用">W"。

添加 2 - 7:54 下午 8/7/2021

(非常感谢@n.1.8e9-where's-my-share m。

我试过这些:

  • __attribute__((weak))添加到 func2.c 中的func2()定义中。

  • 从生成文件中删除-include weak.h

现在这些文件如下所示:

func2.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 说:"弱定义不会改变从库中选择对象文件的规则。但是,如果链接集 包含弱定义和非弱定义, "链接集"是 链接器已加载的对象。它不包括 库中不需要的对象。因此存档两个 包含给定符号的弱定义的对象,以及 另一个包含该符号的非弱定义,成 不建议使用库或单独的库。

添加 3 - 8:47 上午 8/8/2021

正如@n.1.8e9-where's-my-sharem评论的那样:

评论1:

在不是定义的符号上"弱"意味着"不解决" 链接时此符号"。链接者愉快地服从。

评论2:

"在

不是定义的符号上"是错误的,应改为"在 未定义的符号"。

我认为"关于一个未定义的符号">,他的意思是">当前翻译单元中的未定义符号"。就我而言,当我:

  • 在单独的func2.c文件中定义了func2()
  • 并编译了func1.cweak.h

这些实质上告诉链接器不解析翻译单元func1.c中使用的func2()。但似乎这个">不要"仅适用于.a文件。如果我链接除.a文件之外的另一个.o文件,链接器仍然愿意解析func2()。或者,如果func1.c中也定义了func2(),则链接器也会解析它。微妙的!

(到目前为止,所有这些结论都是基于我的实验结果。总结所有这些是微妙的。如果有人能找到一些权威来源,请随时发表评论或回复。谢谢!

(感谢n. 1.8e9-where's-my-share m。的评论。

还有一个相关的线程:

重写 C 中的函数调用

一些事后的想法 - 晚上 9:55 8/8/2021

这些微妙的行为背后没有火箭科学。这只取决于链接器的实现方式。有时文档含糊不清。你必须尝试并处理它。(如果所有这些背后有什么大的想法,请纠正我,我将不胜感激。

这些微妙的行为

这里真的没有什么微妙的东西。

  1. 弱定义意味着:使用此符号,除非还存在另一个强定义,在这种情况下,请使用另一个符号。

    通常,两个同名符号会导致乘法定义链路错误,但是当除一个定义之外的所有定义都较弱时,不会产生乘法定义错误。

  2. 弱(未
  3. 解析)引用意味着:在决定是否从存档库中提取定义此符号的对象时,不要考虑符号(如果对象满足不同的强未定义符号,则仍可能将其拉入)。

    通常,如果在选择所有对象后符号未解析,链接器将报告未解析的符号错误。但是,如果未解析的符号较弱,则会抑制错误。

这就是它的全部内容

更新:

您在评论中重复不正确的理解。

让我感到微妙的是,对于弱引用,链接器不会从存档库中提取对象,但仍然检查独立的对象文件。

这与上面的答案完全一致。当链接器处理存档库时,它必须做出决定:是否选择包含foo.o到链接中。正是决定受到参考类型的影响。

bar.o在链接行上作为"独立对象文件"给出时,链接器不会对此做出任何决定 -bar.o将被选中到链接中。

如果该对象恰好包含弱引用的定义,那么弱引用是否也会被解析?

是的。

即使是弱属性也会告诉链接器不要这样做。

这是误解的明显根源:weak 属性不会告诉链接器不要解析引用;它只告诉链接器(原谅重复)"在决定是否从存档库中提取定义此符号的对象时不要考虑符号"。

我认为这完全取决于是否将包含该弱引用定义的对象拉入以进行链接。

正确。

无论是独立对象还是来自存档库。

错误:始终在链接中选择独立对象。

相关内容

  • 没有找到相关文章

最新更新