c - GCC 错误地优化了自定义地址处变量的指针相等测试

标签 c gcc optimization x86 x86-64

在优化时,GCC 似乎错误地绕过了 #define测试。
首先,我使用的是我自己的 link.ld 链接脚本提供__foo__地址处的符号 0xFFF (实际上是最低位,而不是整个地址):

INCLUDE ./default.ld
__foo__ = 0xFFF;
  • 注意: default.ld 是默认的链接描述文件,通过 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/

    相关文章:

    c - 我的 "Create new nodes"函数和 "add node as last node"函数有什么问题?

    c - 使用 cfitsio 库编译代码时 undefined reference

    c - 储存二十一点手牌的最佳方式是什么?

    sql - 如果索引用于查询,PostgreSQL 将不会使用索引进行投影

    .NET运行时优化服务

    c - 尝试从 LZMA SDK 编译 LzmaUtil.c

    linux - ld 无法识别选项

    c - ncurses 新手 - 从 GNU C 开始

    c++ - Clang 构建错误

    javascript - 性能 - 对于 ajax 请求有多大算太大?