GCC 完全删除 while 循环中的条件

问题描述 投票:0回答:2

我有以下 C/C++ 代码片段:

#define ARRAY_LENGTH 666

int g_sum = 0;
extern int* g_ptrArray[ ARRAY_LENGTH ];

void test()
{
    unsigned int idx = 0;

    // either enable or disable the check "idx < ARRAY_LENGTH" in the while loop
    while( g_ptrArray[ idx ] != nullptr /* && idx < ARRAY_LENGTH */ )
    {
        g_sum += *g_ptrArray[ idx ];
        ++idx;
    }

    return;
}

当我使用版本 12.2.0 中的 GCC 编译器编译上述代码时,两种情况都带有选项

-Os

  1. while 循环条件是
    g_ptrArray[ idx ] != nullptr
  2. while 循环条件是
    g_ptrArray[ idx ] != nullptr && idx < ARRAY_LENGTH

我得到以下组件:

test():
        ldr     r2, .L4
        ldr     r1, .L4+4
.L2:
        ldr     r3, [r2], #4
        cbnz    r3, .L3
        bx      lr
.L3:
        ldr     r3, [r3]
        ldr     r0, [r1]
        add     r3, r3, r0
        str     r3, [r1]
        b       .L2
.L4:
        .word   g_ptrArray
        .word   .LANCHOR0
g_sum:
        .space  4

正如您所看到的,装配体确实如此!不是!对变量

idx
与值
ARRAY_LENGTH
进行任何检查。


我的问题

这怎么可能? 编译器如何为这两种情况生成完全相同的程序集,并忽略代码中存在的

idx < ARRAY_LENGTH
条件?向我解释一下规则或过程,编译器如何得出他可以完全删除条件的结论。

编译器资源管理器中显示的输出程序集(看到两个程序集是相同的):

  1. while条件是

    g_ptrArray[ idx ] != nullptr
    :

  2. while条件是

    g_ptrArray[ idx ] != nullptr && idx < ARRAY_LENGTH
    :

注意:如果我将条件顺序交换为

idx < ARRAY_LENGTH && g_ptrArray[ idx ] != nullptr
,则输出程序集包含对
idx
值的检查,如下所示:https://godbolt.org/z/fvbsTfr9P

c++ arrays c gcc compiler-optimization
2个回答
7
投票

越界访问数组是未定义的行为,因此编译器可以假设它永远不会在

&&
表达式的 LHS 中发生。然后跳过一些环节(优化),注意到由于
ARRAY_LENGTH
是数组的长度,所以 RHS 条件必须成立(否则 UB 会出现在 LHS 中)。这就是你看到的结果。

正确的检查是

idx < ARRAY_LENGTH && g_ptrArray[idx] != nullptr

即使是潜在的未定义行为也可能会做出这样令人讨厌的事情!


0
投票

C 标准 (C17 6.5.6 §8) 规定,我们不能在数组之外进行指针算术,也不能在数组之外访问它 - 这样做是未定义的行为,任何事情都可能发生。

因此,严格来说,数组越界检查是多余的,因为循环条件为“在数组中发现空指针时停止”。如果

g_ptrArray[ idx ]
是越界访问,您将调用未定义的行为,因此理论上程序此时会被烘烤。因此无需计算
&&
的正确操作数。 (您可能知道,
&&
具有严格的从左到右评估。)编译器可以假设访问始终位于已知大小的数组内。

我们可以通过添加一些使编译器无法预测代码的内容来让编译器回归正常:

int** I_might_change_at_any_time = g_ptrArray;
void test2()
{
    unsigned int idx = 0;
     

    // check for idx value is NOT present in code
    while( I_might_change_at_any_time[ idx ] != nullptr && idx < ARRAY_LENGTH)
    {
        g_sum += *g_ptrArray[ idx ];
        ++idx;
    }
}

这里指针充当“中间人”。它是具有外部链接的文件范围变量,因此可能随时更改。编译器不能再假设它始终指向

g_ptrArray
。现在,
&&
的左操作数可以成为明确定义的访问。因此,gcc 现在向汇编程序添加了越界检查:

        cmp     QWORD PTR [rdx+rax*8], 0
        je      .L6
        cmp     eax, 666
        je      .L6
© www.soinside.com 2019 - 2024. All rights reserved.