Блог пользователя ifsmirnov

Автор ifsmirnov, 10 лет назад, По-русски

Дебагая хеш-таблицу, я вчера наткнулся на очередное забавное проявление оптимизатора g++. Вот минимальный код, на котором я смог его воспроизвести.

#include <iostream>

int main() {
    for (int i = 0; i < 4; ++i) {
        std::cout << i*1000000000 << std::endl;
    }   
}

Казалось бы, вывод предсказуем. Более того, без оптимизации это действительно так. Однако...

ifsmirnov@carbon:./tmp$ g++-4.8 a.cpp -O2 && ./a.out  | head -n 10
0
1000000000
2000000000
-1294967296
-294967296
705032704
1705032704
-1589934592
-589934592
410065408

И так бесконечно долго.

Логическая цепочка оптимизатора понятна: по стандарту переполнение инта при умножении -- это UB, значит, можно делать всё, что угодно. Но во что превратилось условие остановки цикла, я так и не понял.

У меня это воспроизводится на g++-4.8 c -O2 и выше. На 4.4, 4.6 и 4.7, clang-3.4 выводятся четыре числа даже с любыми уровнями оптимизации.

Интересно, что по этому поводу думает MinGW, а также почему все-таки происходит этот эффект.

UPD: если заменить cout на printf, «баг» не вылезает.

UPD2: продолжаем развлекаться. Если вместо вывода делать push_back в вектор, баг вылезает. Если же вместо этого написать свой «вектор», то можно поймать нетривиальный warning.

#include <cstdlib>

struct MyVector {
    int *a;
    int cap;
    int n;
    MyVector() {
        cap = 1;
        n = 0;
        a = (int*)malloc(sizeof(int) * cap);
    }   
    void push_back(int x) {
        if (n == cap) {
            cap *= 2;
            a = (int*)realloc((void*)a, sizeof(int) * cap);
        }   
        a[n++] = x;
    }   
};

int main() {
    MyVector a;
    for (int i = 0; i < 4; ++i) {
        a.push_back(i * 1000000000);
    }   
}

b.cpp: In function ‘int main()’:
b.cpp:24:35: warning: iteration 2u invokes undefined behavior [-Waggressive-loop-optimizations]
         a.push_back(i * 1000000000);
                                   ^
b.cpp:23:5: note: containing loop
     for (int i = 0; i < 4; ++i) {
     ^
Теги ub, c++
  • Проголосовать: нравится
  • +52
  • Проголосовать: не нравится

»
10 лет назад, # |
  Проголосовать: нравится +1 Проголосовать: не нравится

У меня с MinGW g++ 4.8.1 тоже бесконечный вывод чисел.

Отличная программа!

»
10 лет назад, # |
  Проголосовать: нравится +5 Проголосовать: не нравится

Хорошая тулза, чтобы ASM сравнить: http://gcc.godbolt.org/

»
10 лет назад, # |
Rev. 2   Проголосовать: нравится +26 Проголосовать: не нравится

я просто оставлю это здесь

»
10 лет назад, # |
  Проголосовать: нравится +95 Проголосовать: не нравится

Вероятно, оптимизатор убрал "лишнее умножение":

#include <iostream>

int main() {
    for (int i = 0; i < 4000000000; i += 1000000000) {
        std::cout << i << std::endl;
    }   
}
  • »
    »
    10 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится +21 Проголосовать: не нравится

    Сгенерировал ассемблерный листинг с помощью g++ -O2 -S -c foo.cpp. Ни одного умножения, зато присутствует строка addl $1000000000, %r12d. Так что похоже, что таки да, убирает лишнее умножение.

    P.S. g++ версии 4.9.1, тоже бесконечный цикл.

»
10 лет назад, # |
  Проголосовать: нравится +8 Проголосовать: не нравится

Что забавно, если заменить endl на '\n', то баг опять перестает проявляться.

  • »
    »
    10 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    std::endl — это на самом деле функция. Вероятно, вызов функции в том месте что-то меняет.