Дебагая хеш-таблицу, я вчера наткнулся на очередное забавное проявление оптимизатора 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) {
^
У меня с MinGW g++ 4.8.1 тоже бесконечный вывод чисел.
Отличная программа!
Хорошая тулза, чтобы ASM сравнить: http://gcc.godbolt.org/
я просто оставлю это здесь
Вероятно, оптимизатор убрал "лишнее умножение":
Сгенерировал ассемблерный листинг с помощью g++ -O2 -S -c foo.cpp. Ни одного умножения, зато присутствует строка
addl $1000000000, %r12d
. Так что похоже, что таки да, убирает лишнее умножение.P.S. g++ версии 4.9.1, тоже бесконечный цикл.
Что забавно, если заменить endl на '\n', то баг опять перестает проявляться.
std::endl
— это на самом деле функция. Вероятно, вызов функции в том месте что-то меняет.