gpt4 book ai didi

c++ - __attribute __((weak))和静态库

转载 作者:行者123 更新时间:2023-12-03 18:28:16 31 4
gpt4 key购买 nike

我想在代码中引入一个弱符号,但是,当使用* .a文件时,我无法理解它的行为。

这是我的最小示例:

文件a.h:

void foo() __attribute__((weak));

归档交流:
#include "a.h"
#include <stdio.h>

void foo() { printf("%s\n", __FILE__); }

文件BC:
#include <stdio.h>

void foo() { printf("%s\n", __FILE__); }

文件main.cpp:
#include "a.h"
#include <stdio.h>

int main() { if (foo) foo(); else printf("no foo\n"); }

现在,根据我使用* .o文件( make -c a.cmake -c b.c)还是* .a文件( ar cr a.oar cr b.o),输出有所不同:
1) g++ main.cpp a.o b.o打印b.c
2) g++ main.cpp b.o a.o打印b.c
3) g++ main.cpp a.a b.a不打印foo
4) g++ main.cpp b.a a.a不打印foo

1),2)工作正常,但3),4)的输出似乎有点出乎意料。

我拼命试图使此示例与文件一起使用,所以我做了一些更改:

文件a.h:
void foo();

归档交流:
#include "a.h"
#include <stdio.h>

void __attribute__((weak)) foo() { printf("%s\n", __FILE__); }

修改之后:

1) g++ main.cpp a.a b.a打印a.c
2) g++ main.cpp b.a a.a打印b.c

因此效果更好。运行 nm a.a后显示 W _Z3foov,因此不会违反ODR。但是,我不知道这是否是弱属性的正确用法。根据gcc文档:

The weak attribute causes the declaration to be emitted as a weak symbol rather than a global. This is primarily useful in defining library functions which can be overridden in user code, though it can also be used with non-function declarations. Weak symbols are supported for ELF targets, and also for a.out targets when using the GNU assembler and linker.



但是我在函数定义而不是声明上使用了弱属性。

所以问题是为什么弱不能与* .a文件一起使用?在定义而不是声明上使用弱属性是否正确?

更新

令我惊奇的是,与foo()方法定义一起使用的弱属性对符号解析没有影响。没有属性,最终二进制文件将生成相同的结果:

1) g++ main.cpp a.a b.a打印a.c
2) g++ main.cpp b.a a.a打印b.c

因此,仅使用符号的第一个定义,这与默认的gcc行为一致。即使 nm a.a显示发出了一个弱符号,它似乎也不影响静态链接。

是否可以将弱属性与静态链接一起使用?

我要解决的问题的说明

我有一个供超过20个客户端使用的库,我们将其称为库A。我还提供了一个库B,其中包含针对A的测试实用程序。某种程度上,我需要知道库A用于测试模式,因此似乎是最简单的解决方案在与B链接期间替换符号(因为客户端已经与B链接)。

我知道有解决此问题的更干净的方法,但是我绝对不能影响客户的代码或他们的构建脚本(添加参数以表明对A进行测试或对DEFINE进行编译是不可行的)。

最佳答案

为了解释这里发生的事情,让我们先谈谈您的原始源文件,

a.h(1):

void foo() __attribute__((weak));

和:

a.c(1):
#include "a.h"
#include <stdio.h>

void foo() { printf("%s\n", __FILE__); }

示例代码中 .c.cpp文件的混合与
问题,并且所有代码都是C,所以我们说 main.cppmain.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的一个弱定义而没有强定义
    定义,链接器不可避免地使用一个弱定义。
  • 如果链接包含foo的多个弱定义且不包含强
    定义,然后链接器会任意选择一个弱定义之一。

  • 接下来,让我们回顾一下在链接中输入目标文件之间的区别
    并输入一个静态库。

    静态库只是我们可能提供给目标文件的 ar存档
    从链接器中选择需要进行链接的链接器。

    当目标文件输入到链接时,链接器无条件链接它
    进入输出文件。

    当将静态库输入链接时,链接器将检查存档以
    在其中找到任何提供 undefined symbol 引用所需定义的目标文件
    从已经链接的输入文件中产生的结果。如果找到任何此类目标文件
    在文件中,它提取它们并将它们链接到输出文件中,就像
    如果它们分别命名为输入文件,并且根本没有提及静态库。

    考虑到这些观察结果,请考虑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.ob.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

    foo的弱引用不需要定义。因此链接器将
    不要尝试在 a.ab.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.ab.a的链接顺序也没关系,
    但这是因为它们都不对链接有任何贡献。

    现在让我们转到通过更改 a.ha.c发现的不同行为
    至:

    a.h(2):
    void foo();

    a.c(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_

    因此,代码(链接时)必须具有 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

    您真正的问题...

    ...出现在您对帖子的评论之一中:

    I want to override [the] original function implementation when linking with a testing framework.



    您要链接输入一些静态库 lib1.a的程序
    它具有一些定义符号 file1.o的成员 foo,并且您想删除 foo的定义,并链接其他对象中定义的另一个
    文件 file2.o
    __attribute__((weak))不适用于该问题。解决方案更多
    初级。您只需确保在输入链接之前输入 file2.o lib1.a(并且在提供 foo定义的任何其他输入之前)。
    然后,链接程序将使用 foo中提供的定义来解析对 file2.o的引用,并且不会尝试查找任何其他引用
    到达 lib1.a时的定义。链接器完全不会使用 lib1.a(file1.o)。它可能不存在。

    如果将 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本身。

    最后

    Is it possible to use [the] weak attribute with static linking at all?



    当然。这是第一个原理用例:

    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);
    }

    程序
    #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.obb.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.obb.o都已加载,并且每个都包含 foo的定义,没有多定义错误结果,因为每个定义
    弱。在 aa.o之前加载了 bb.o,并且从 foo链接了 aa.o的定义。

    那么 foobb.o的定义发生了什么?映射文件向我们显示:

    映射文件(1)
    ...
    ...
    Discarded input sections
    ...
    ...
    .text.foo 0x0000000000000000 0x13 bb.o
    ...
    ...

    链接器丢弃了包含定义的功能部分
    bb.o
    让我们颠倒 aa.obb.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.oaa.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

    现在 fooaa.o中的每个对 bb.o(C++错位)的定义都很弱:
    $ 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.obb.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实现。

    关于c++ - __attribute __((weak))和静态库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51656838/

    31 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com