GCC -O2 和 __attribute__((弱))

GCC -O2 and __attribute__((weak))

看起来 -O2__attribute__((weak)) 的 GCC 会根据您引用弱符号的方式产生不同的结果。考虑一下:

$猫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" );
    }
}

$猫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();
}

$ 制作

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

$./测试

1
1
1

$ 制作 ADD_FLAGS="-O2"

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

$./测试

0
1
1

问题是,为什么最后一个“./test”产生“0 1 1”,而不是“1 1 1”?

gcc 版本 5.4.0 (GCC)

看起来在进行优化时,编译器在声明 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

总结:

弱符号只有在您未将它们初始化为值时才能正常工作。链接器负责初始化(如果不存在同名的正常符号,它总是将它们初始化为零)。

如果您尝试将弱符号初始化为任何值,甚至像 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语言没有“weak symbol”的概念。它是由 ELF 文件格式和使用 ELF 文件格式的(动态)链接器提供的功能。

正如 man 1 nm 手册页在 "V" 部分所述,

When a weak defined symbol is linked with a normal defined symbol, the normal defined symbol is used with no error. When a weak undefined symbol is linked and the symbol is not defined, the value of the weak symbol becomes zero with no error.

不应将弱符号声明初始化为任何值,因为如果进程未与同名的普通符号链接,它将具有零值。 (man 1 nm页面中的"defined"指的是ELF符号table中存在的一个符号。)

"weak symbol" 功能旨在与现有的 C 编译器一起使用。请记住,C 编译器在 "weak" 和 "normal" 符号之间没有任何区别。

为确保这在 运行 不影响 C 编译器行为的情况下工作,必须未初始化 "weak" 符号,以便 C 编译器无法对其值做出任何假设。相反,它会像往常一样生成获取符号地址的代码——这就是 normal/weak 符号查找魔术发生的地方。

这也意味着弱符号只能 "auto-initialized" 为零,而不是任何其他值,除非 "overridden" 由同名的正常初始化符号。