Компиляция с оптимизацией получает условие неправильно - PullRequest
4 голосов
/ 23 февраля 2020

Этот фрагмент кода содержит ошибку в условии завершения l oop. Тем не менее, я до сих пор не понимаю решение компилятора - похоже, он снова попадает в l oop.

#include <stdio.h>
#include <string.h>

int main(int argc, char* argv[])
{
    #define ARR_SIZE 25
    int a[ARR_SIZE];
    memset (a,1,sizeof(a)); /*filling the array with non-zeros*/

    int i = 0;

    for (i=0; (a[i] != 0 && i < ARR_SIZE); i++)
    {
        printf ("i=%d a[i]=%d\n",i,a[i]);
    }
    return 0;
}

При компиляции с -O2 или -O3 l oop не завершается, когда ожидается - он печатает также строку, когда i == ARR_SIZE.

> gcc -O3  test_loop.c
> ./a.out
i=0 a[i]=16843009
i=1 a[i]=16843009
...
i=23 a[i]=16843009
i=24 a[i]=16843009
i=25 a[i]=32766      <=== Don't understand this one.

>  gcc -O0  test_loop.c
>  a.out
i=0 a[i]=16843009
i=1 a[i]=16843009
...
i=23 a[i]=16843009
i=24 a[i]=16843009
>

Версия g cc такова: gcc version 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC)

Не знаю увидеть это происходит на gcc 4.4.7-18.

Также другие размеры ARR_SIZE не дают таких же результатов.

Ответы [ 2 ]

7 голосов
/ 23 февраля 2020

Когда i == ARR_SIZE ваше состояние будет оцениваться a[i], вызывая UB

for (i=0; (a[i] != 0 && i < ARR_SIZE); i++)
//         ^^^^ Undefined Behaviour
{
    printf ("i=%d a[i]=%d\n",i,a[i]);
}

Поменяйте местами условия: for (... (i < ARR_SIZE && a[i] != 0) ...), чтобы воспользоваться преимуществом "логической оценки короткого замыкания".

3 голосов
/ 23 февраля 2020

Современные компиляторы замечают неопределенное поведение и могут использовать его как оправдание для генерации «неожиданного» кода, и это то, что вы испытываете. См. 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
...