GCC -O2 and __attribute__((weak))

5
似乎GCC在使用-O2__attribute__((weak))时,根据您如何引用弱符号而产生不同的结果。请考虑以下代码: $ cat weak.c
#include <stdio.h>

extern const int weaksym1;
const int weaksym1 __attribute__(( weak )) = 0;

extern const int weaksym2;
const int weaksym2 __attribute__(( weak )) = 0;

extern int weaksym3;
int weaksym3 __attribute__(( weak )) = 0;

void testweak(void)
{
    if ( weaksym1 == 0 )
    {
        printf( "0\n" );
    }
    else
    {
        printf( "1\n" );
    }

    printf( "%d\n", weaksym2 );


    if ( weaksym3 == 0 )
    {
        printf( "0\n" );
    }
    else
    {
        printf( "1\n" );
    }
}

$ cat test.c

extern const int weaksym1;
const int weaksym1 = 1;

extern const int weaksym2;
const int weaksym2 = 1;

extern int weaksym3;
int weaksym3 = 1;

extern void testweak(void);

void main(void)
{
    testweak();
}

$ make

gcc  -c weak.c
gcc  -c test.c
gcc  -o test test.o weak.o

$ ./test

1
1
1

$ make ADD_FLAGS="-O2"

gcc -O2 -c weak.c
gcc -O2 -c test.c
gcc -O2 -o test test.o weak.o

$ ./test

0
1
1

问题是,为什么最后一个"./test"生成的是"0 1 1",而不是"1 1 1"?
gcc版本5.4.0(GCC)

在这种情况下,第二次运行将打印“0 0 0”。 - user3234859
gcc手册几乎没有提供有关弱属性对变量的作用的信息。 - M.M
1
@user3234859: 你不应该初始化弱变量,如果没有非弱符号的话它们将会被“初始化”为零。这就是让编译器出现问题的原因。请参考man 1 nm中对于“V”的描述。如果你移除了零初始化,你会发现你的代码会可预测地执行,无论优化或引用类型/方法如何。 - Nominal Animal
2个回答

3

看起来在进行优化时,编译器处理在同一编译单元内声明为const并具有weak定义的符号时存在问题。

你可以创建一个单独的c文件,并将const weak定义移动到那里,这将解决该问题:

weak_def.c

const int weaksym1 __attribute__(( weak )) = 0;
const int weaksym2 __attribute__(( weak )) = 0;

这个问题在这个帖子中有描述:GCC weak attribute on constant variables


谢谢提供链接,不过可以看到问题只出现在-O2下(我认为这也是他们的情况,只是可能错过了),而且在我的示例代码中,weaksym3当然是有意声明为非const的。 - user3234859
实际上,任何优化级别都是这样。 - user3234859
1
@nnn:你根本不应该初始化弱变量。如果没有与其同名的非弱符号链接到二进制文件中,它们将为零。将它们初始化为零会让编译器感到困惑,因为编译器无法理解弱符号的行为;这种魔法发生在(动态)链接时。 - Nominal Animal
好的,将弱变量移动到单独的模块中可以有效地禁用它们的优化,无论它们是否被初始化,并且允许保持常数值 - 这正是我所需的(不知道这是否适用于弱函数?)。 - user3234859
这里对于零的初始化并不是必需的,但在某些特定情况下,您可能希望弱默认值为某个非零值。 - nnn
@nnn:将一个弱变量初始化为非零变量必须在任何访问它的编译单元中进行单独处理,否则编译器可能会对其值做出假设。(这只能通过完全省略初始化并依赖链接器将其设置为零来避免。)这是 OP 问题的根源。 - Nominal Animal

3

摘要:

弱符号只有在不将它们初始化为一个值时才能正常工作。链接器会负责初始化(如果没有同名的普通符号,则始终将它们初始化为零)。

如果您尝试将弱符号初始化为任何值,即使是像 OP 一样的零,C 编译器也可以对其值进行奇怪的假设。编译器没有弱符号和普通符号之间的区别;这都是(动态)链接器的魔法。

要修复问题,请从您声明为弱的任何符号中删除初始化(= 0):

extern const int weaksym1;
const int weaksym1 __attribute__((__weak__));

extern const int weaksym2;
const int weaksym2 __attribute__((__weak__));

extern int weaksym3;
int weaksym3 __attribute__((__weak__));

详细描述:
C语言没有"弱符号"的概念,这是ELF文件格式和使用该格式的(动态)链接器提供的功能。
正如man 1 nm手册在"V"部分描述的那样,
当一个弱定义符号与一个普通定义符号链接时,将使用普通的定义符号而不会出现错误。当一个弱未定义符号被链接且该符号未被定义时,弱符号的值变为零且不会出现错误。
弱符号的声明不应初始化为任何值,因为如果进程未与同名的普通符号链接,它将具有值零。在 man 1 nm 页面中,“定义”指的是存在于ELF符号表中的符号。
"弱符号"功能是为了与现有的C编译器配合使用而设计的。请记住,C编译器没有任何区分“弱”和“正常”符号的区别。
为确保不违反C编译器行为,"弱"符号必须未初始化,这样C编译器就不能对其值做出任何假设。相反,它将生成通常获取符号地址的代码,这就是正常/弱符号查找魔法发生的地方。
这也意味着弱符号只能自动初始化为零,而不能初始化为其他任何值,除非被同名的正常初始化符号"覆盖"。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接