在编译/链接期间由 GCC 确定的符号值

Posted

技术标签:

【中文标题】在编译/链接期间由 GCC 确定的符号值【英文标题】:The value of a symbol determined by GCC during compiling/linking 【发布时间】:2013-05-28 08:34:45 【问题描述】:

为什么在下面的代码中“if (sqrt)”条件总是正确的?编译的时候gcc这样警告,

weak.c: In function âfâ:
weak.c:6: warning: the address of âsqrtâ, will always evaluate as âtrueâ

源码,

#include <stdio.h>

extern double sqrt(double x); /* DECLARATION VERSION 1 */
/* extern double sqrt(double x) __attribute__ ((weak)); */ /* DECLARATION VERSION 2 */

void f() 
        if (sqrt) 
                printf("sqrt of 10 %f \n", sqrt(10.0));
        
        else 
                printf("sqrt not found \n");
        


int main (int arg, char **argv)

        f();
        return 0;

如果我注释掉行-“声明版本 1”并取消注释行-“声明版本 2”,我有一个具有以下行为的二进制文件。

当二进制文件与 -lm 链接时,“if (sqrt)”为真。 如果不是,则条件为假。我知道/认为对于一个弱符号,这里是“sqrt”函数,链接时它被初始化为 0。因此,当“sqrt”符号在其他地方(库)找不到它的定义时,“if (sqrt)”条件为假。

但是,当我使用“声明版本 1”时,为什么“sqrt”总是非零?在“DECLARATION VERSION 1”中,“sqrt”不是弱符号,当然也不是强符号。实际上,当您使用“nm”命令时,您找不到“sqrt”符号。而且,当我尝试打印“sqrt”的地址时,我得到一个编译错误。

有人知道“sqrt”不为零的原因吗?

提前致谢!

由于我找不到合适的标题来描述我的问题,如果有的话,请推荐一个更好的标题。

我想我找到了原因:

gcc 有一个用于优化目的的内置 sqrt() 函数。如果可能,gcc 将用其内置的 sqrt() http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html 替换 libm 中对 sqrt() 的调用。编译时,如果未关闭内置函数且未给出 libm(-lm),gcc 将使用其内置版本的 sqrt(),它始终具有非零地址。 当我关闭内置时,我得到一个编译错误

$ gcc -fno-builtin weak.c
weak.c: In function âfâ:
weak.c:9: warning: the address of âsqrtâ, will always evaluate as âtrueâ
/tmp/cc84fwK9.o: In function `f':
weak.c:(.text+0x11): undefined reference to `sqrt'
weak.c:(.text+0x29): undefined reference to `sqrt'
collect2: ld returned 1 exit status

【问题讨论】:

【参考方案1】:

在这两种情况下,sqrt() 都是一个函数。对于decl ver 1,在if语句中:

if (sqrt)

sqrt 解析为该函数的 地址,该地址在编译时是已知的(无论如何不能为零)。因为在 C 语言中,任何非零都为真,if 语句的条件,函数的地址,总是计算为真 :)

当您使用__attribute__ ((weak)) 时,编译器允许您“检查”函数是否存在。如果不是,则仍会创建符号,但将其设置为 NULL。因此,当您将其设为“弱”链接函数时,符号 sqrt 可能是 null(false)。

From wikipedia...

在计算中,弱符号是目标文件中的符号定义 或可能被其他符号定义覆盖的动态库。 如果加载器没有找到定义,它的值将为零。

总而言之,在 decl 版本 1 中,链接器必须在编译时找到函数 sqrt,并将函数的地址绑定到该符号。因此符号不能为空。

在 decl 版本 2 中,链接器不需要找到 sqrt。如果是,sqrt 将解析为函数地址,但如果它没有找到该函数,它将将该符号设置为 null。即,弱符号允许您定义不需要在链接时解析的符号...

同样来自GNU Compiler Function Attributes

weak 属性导致声明被作为弱 符号而不是全局。这主要用于定义 可以在用户代码中覆盖的库函数,尽管它可以 也可用于非函数声明。弱符号是 支持 ELF 目标,并且在使用 GNU 汇编器和链接器。

这不仅允许您检查函数是否存在,而且还允许您(如上所述)覆盖函数。请参阅Understand Weak Symbols by Examples 以获取一个简洁的小示例和一些有用的参考资料(特别好 - GCC Weak Symbols - 给出了弱函数符号被强函数符号覆盖的示例)。

【讨论】:

嗨,Jimbo,您说“sqrt 解析为该函数的地址,该地址在编译时已知(无论如何不能为零)”。但是,我编译代码时没有传递“-lm”选项,因此 gcc 将找不到 sqrt 函数的地址/定义。那么它的地址是如何在编译时知道的呢? 我认为是因为sqrt 是标准库的一部分并且默认链接到。 m 是做什么的?我怀疑标准库是默认链接的……不是 100% 确定的。但对于某些符号,在编译(以及链接)时解析:)我认为您需要使用链接器选项-nostdlib 来停止与标准库的链接。这就是为什么,我怀疑,您仍然可以解析 sqrt 符号...(请参阅 gcc.gnu.org/onlinedocs/gcc/Link-Options.html) 我并不是建议您不要链接到标准库顺便说一句...这只是说明为什么该符号仍然存在于您的示例:) 好的,这就是编译时知道符号的方式。我不知道内置插件,所以感谢您发布您发现的内容:)

以上是关于在编译/链接期间由 GCC 确定的符号值的主要内容,如果未能解决你的问题,请参考以下文章

Linux上静态库和动态库的编译和使用

GCC链接器:在指定部分移动符号

ELF 动态链接 - so 的 重定位表

gcc -u 符号编译器选项

gcc编译器选项-Wl,--no-undefined(告诉链接器在链接过程中不允许有未定义的符号)(gcc编译器和链接器是分离的工具,它们需要通过选项来进行通信)

gcc 在链接时忽略符号名称的大小写