Современные компиляторы замечают неопределенное поведение и могут использовать его как оправдание для генерации «неожиданного» кода, и это то, что вы испытываете. См. https://godbolt.org/z/SEZKBZ, вам нужно go вернуться к 4.6.x, чтобы сравнение i < ARR_SIZE
появилось в оптимизированном скомпилированном коде (который на самом деле не очень оптимизирован для этой старой версии) :
...
call printf
lea eax, [rbx+1]
mov edx, DWORD PTR [rsp+4+rbx*4]
cmp eax, 24
setle cl
test edx, edx
setne al
add rbx, 1
test cl, al
jne .L3
Более высокие версии содержат только нулевой тест:
...
call printf
mov edx, DWORD PTR [rsp+rbx*4]
test edx, edx
jne .L8
Если вы проверите первую часть оптимизированных скомпилированных кодов, вы увидите, что вызов memset()
получил встроенный и развернутый, поэтому компилятор точно знает, что находится в массиве и что условие l oop будет индексировать его (массив) до выхода (так как внутри нет нуля). И тогда его уже не волнует другое состояние.
Аналогичным образом, если вы исправите код на
a[i] != 0 && i < ARR_SIZE
, как это было предложено, компилятор по-прежнему будет знать, что в массиве нет нулей, и оптимизирует проверку нуля, как раз в этот раз оптимизация правильного кода приводит к правильному поведению. :
call printf
cmp rbx, 25
je .L6