在优化时,GCC 似乎错误地绕过了 #define
测试。
首先,我使用的是我自己的 link.ld 链接脚本提供__foo__
地址处的符号 0xFFF
(实际上是最低位,而不是整个地址):
INCLUDE ./default.ld
__foo__ = 0xFFF;
gcc ... -Wl,-verbose
获得命令结果 然后,一个 foo.c 源文件检查
__foo__
的地址:#include <stdint.h>
#include <stdio.h>
extern int __foo__;
#define EXPECTED_ADDR ((intptr_t)(0xFFF))
#define FOO_ADDR (((intptr_t)(&__foo__)) & EXPECTED_ADDR)
#define FOO_ADDR_IS_EXPECTED() (FOO_ADDR == EXPECTED_ADDR)
int main(void)
{
printf("__foo__ at %p\n", &__foo__);
printf("FOO_ADDR=0x%lx\n", FOO_ADDR);
printf("EXPECTED_ADDR=0x%lx\n", EXPECTED_ADDR);
if (FOO_ADDR_IS_EXPECTED())
{
printf("***Expected ***\n");
}
else
{
printf("### UNEXPECTED ###\n");
}
return 0;
}
我期待 ***Expected ***
打印消息,如 FOO_ADDR_IS_EXPECTED()
应该是真的。编译
-O0
选项,它按预期执行:$ gcc -Wall -Wextra -Werror foo.c -O0 -o foo_O0 -T link.ld && ./foo_O0
__foo__ at 0x5603f4005fff
FOO_ADDR=0xfff
EXPECTED_ADDR=0xfff
***Expected ***
但与 -O1
选项,它不会:$ gcc -Wall -Wextra -Werror foo.c -O1 -o foo_O1 -T link.ld && ./foo_O1
__foo__ at 0x5580202d0fff
FOO_ADDR=0xfff
EXPECTED_ADDR=0xfff
### UNEXPECTED ###
这是-O0
中的拆解:$ objdump -d ./foo_O0
...
0000000000001169 <main>:
...
11b5: b8 00 00 00 00 mov $0x0,%eax
11ba: e8 b1 fe ff ff callq 1070 <printf@plt>
11bf: 48 8d 05 39 fe ff ff lea -0x1c7(%rip),%rax # fff <__foo__>
11c6: 25 ff 0f 00 00 and $0xfff,%eax
11cb: 48 3d ff 0f 00 00 cmp $0xfff,%rax
11d1: 75 0e jne 11e1 <main+0x78>
11d3: 48 8d 3d 5e 0e 00 00 lea 0xe5e(%rip),%rdi # 2038 <_IO_stdin_used+0x38>
11da: e8 81 fe ff ff callq 1060 <puts@plt>
11df: eb 0c jmp 11ed <main+0x84>
11e1: 48 8d 3d 60 0e 00 00 lea 0xe60(%rip),%rdi # 2048 <_IO_stdin_used+0x48>
11e8: e8 73 fe ff ff callq 1060 <puts@plt>
11ed: b8 00 00 00 00 mov $0x0,%eax
...
我不是专家,但我可以看到 jne
条件和两次调用 puts
,匹配 if (FOO_ADDR_IS_EXPECTED())
陈述。这是
-O1
中的拆解:$ objdump -d ./foo_O1
...
0000000000001169 <main>:
...
11c2: b8 00 00 00 00 mov $0x0,%eax
11c7: e8 a4 fe ff ff callq 1070 <__printf_chk@plt>
11cc: 48 8d 3d 65 0e 00 00 lea 0xe65(%rip),%rdi # 2038 <_IO_stdin_used+0x38>
11d3: e8 88 fe ff ff callq 1060 <puts@plt>
...
这一次,我看不到任何条件,直接拨打 puts
(对于 printf("### UNEXPECTED ###\n");
声明)。为什么是
-O1
优化修改行为?为什么要优化FOO_ADDR_IS_EXPECTED()
是假的?一些有助于您分析的上下文:
$ uname -rm
5.4.0-73-generic x86_64
$ gcc --version
gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
编辑:出人意料的是,修改了
0xFFF
值到 0xABC
改变行为:$ gcc -Wall -Wextra -Werror foo.c -O0 -o foo_O0 -T link.ld && ./foo_O0
__foo__ at 0x5653a7d4eabc
FOO_ADDR=0xabc
EXPECTED_ADDR=0xabc
***Expected ***
$ gcc -Wall -Wextra -Werror foo.c -O1 -o foo_O1 -T link.ld && ./foo_O1
__foo__ at 0x564323dddabc
FOO_ADDR=0xabc
EXPECTED_ADDR=0xabc
***Expected ***
正如 Andrew Henle 所指出的,地址对齐似乎很重要:使用 0xABF
而不是 0xABC
产生与 0xFFF
相同的结果.
最佳答案
如 @AndrewHenle和 @chux-ReinstateMonica建议,这是一个对齐问题。__foo__
变量类型是 int
: 它的地址应该是 32 位对齐的,意思是可以被 4 整除。0xFFF
不能被 4 整除,因此编译器假定它不是有效的 int
地址:它优化相等测试为假。
换 __foo__
的类型为 char
删除对齐约束,行为在 -O0
中保持不变和 -O1
:
// In foo.c
...
extern char __foo__;
...
$ gcc -Wall -Wextra -Werror foo.c -O0 -o foo_O0 -T link.ld && ./foo_O0
__foo__ at 0x55fbf8bedfff
FOO_ADDR=0xfff
EXPECTED_ADDR=0xfff
***Expected ***
$ gcc -Wall -Wextra -Werror foo.c -O1 -o foo_O1 -T link.ld && ./foo_O1
__foo__ at 0x5568d2debfff
FOO_ADDR=0xfff
EXPECTED_ADDR=0xfff
***Expected ***
关于c - GCC 错误地优化了自定义地址处变量的指针相等测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68397045/