代码之家  ›  专栏  ›  技术社区  ›  Bludzee

带内建函数的奇异gcc6优化

  •  1
  • Bludzee  · 技术社区  · 7 年前

    使用gcc6和下面的代码片段,这个测试

    if (i > 31 || i < 0) {
    

    ,执行此printf

    printf("i > 31 || i < 0 is FALSE, where i=%d", i);
    

    并产生这个非常奇怪的输出(gcc6):

    31英镑 ||我<0是 错误的 在哪里 i=32 /*GCC6的奇怪结果!!!!*/

    而对于GCC4,我得到:

    31英镑 ||我<0是 在哪里 i=32 /*结果符合GCC4 */

    看起来很好。

    怎么会这样??

    代码段(损坏的旧代码!):

    static int check_params(... input parameters ...) {
        /* Note that value can be 0 (zero) */
        uint32_t value = ....
        int i;
    
        i = __builtin_ctz(value);
        if (i > 31 || i < 0) {
            printf("i > 31 || i < 0 is true, where i=%d", i);
            /* No 1 found  */
            return 0;
        } else {
            printf("i > 31 || i < 0 is FALSE, where i=%d", i);
        }
        return i;
    }
    

    根据 the documentation about GCC builtin functions ,必须避免调用内置CTZ(0):

    内置函数:int _内置CTZ (unsigned int x)返回 x中的尾随0位数,至少从有效位开始 位置。如果x是0, 结果未定义 .

    显然,解决编码错误的方法是在调用builtin ctz(value)之前简单地检查值。这是清楚和明白的。

    我可以停下来谈谈别的话题… 但是,我仍然不明白我怎么可能(在断开代码的情况下)得到以下输出:

    31英镑 ||我<0是 错误的 在哪里 i=32 /*GCC6的奇怪结果!!!!*/

    奇怪的GCC6优化还是什么?

    以防万一:

    Cross-compiler: arm-linux-gcc
    Architecture: -march=armv7-a
    

    有什么想法吗?

    4 回复  |  直到 7 年前
        1
  •  6
  •   sepp2k    7 年前

    除非行为不明确, __builtin_ctz 将始终返回一个介于0和31之间的数字,gcc知道这一点。所以支票 i > 31 || i < 0 将始终是错误的(再次排除未定义的行为),并且可以优化。

    如果查看生成的程序集,您将看到该条件根本没有出现在代码中(then case也没有)。

        2
  •  3
  •   Jacob Krall    7 年前

    未定义的行为并不意味着“值将是任意的”。它意味着编译器可以 literally anything it wants to . 在本例中,编译器似乎能够静态地验证 只要 value 不是0 , i 将始终介于0和31之间(包括0和31)。所以它甚至不需要为then子句生成代码。

    你只是幸运的恶魔没有从你鼻子里出来。

    参见: Undefined behavior can result in time travel , The premature downcast , Why undefined behavior may call a never-called function 以及 many many many other discussions of UB 在这里。

        3
  •  1
  •   rici    7 年前

    编译器假定未定义的行为不会发生。它可以做出这样的假设,因为如果违反了约束并且行为未定义, 任何 结果是可能的,包括错误假设可能导致的结果。

    如果没有未定义的行为,那么 i 不能为负或大于31。在此基础上, if 语句可以在编译时优化。

    实际打印的值 printf 无法预测,所以它实际上调用 普林特 不管怎样 碰巧是。在这种情况下,它碰巧是32,但它可能是任何东西。

        4
  •  0
  •   supercat    7 年前

    c标准指出,实现通常将未定义的行为视为对编译器的邀请,使其以环境的文档化方式进行操作,而基本原理则指出,将行为分类为ub是为了获得高质量的实现。在市场需求时提供超出标准要求的功能。尽管如此,从他们的行为来看,一些编译器供应商认为让编译器寻找更聪明的方法来利用无意义地处理某些情况的自由比让他们在实际情况下采用合理约束的行为(例如_ builtin_ctz()以未指定的方式在执行CPU所做的任何操作或生成某些任意值之间进行选择)。

    我个人很怀疑,任何“优化”的价值,如果允许编译器做任何事情,而不是产生一个值或执行CPU操作,无论结果如何,都会接近允许程序员假设在cpu本身不做任何奇怪事情的平台上,该操作只会产生一个未指定的结果,并且没有副作用。尽管如此,gcc的作者似乎认为,要求程序员添加额外的源代码来处理不需要额外机器代码就可以处理的情况,这将在某种程度上提高“效率”。

    推荐文章