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" 由同名的正常初始化符号。
看起来 -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" 由同名的正常初始化符号。