Даже со «знакомым» компилятором, таким как g cc, на «знакомой» платформе, такой как x86, переполнение целых чисел со знаком может делать что-то иное, чем «очевидное» поведение с двойным дополнением.
One Забавным (или, возможно, ужасающим) примером является следующий ( см. на Годболте ):
#include <stdio.h>
int main(void) {
for (int i = 0; i >= 0; i += 1000000000) {
printf("%d\n", i);
}
printf("done\n");
return 0;
}
Наивно, вы ожидаете, что это выведет
0
1000000000
2000000000
done
И с gcc -O0
ты был бы прав. Но с gcc -O2
вы получаете
0
1000000000
2000000000
-1294967296
-294967296
705032704
...
, продолжающийся бесконечно. Арифметика c является двойным дополнением, все в порядке, но, похоже, что-то пошло не так при сравнении в условии l oop.
На самом деле, если вы посмотрите на вывод сборки, вы Вы увидите, что g cc полностью пропустил сравнение и сделал l oop безусловно бесконечным. Он может сделать вывод, что если бы не было переполнения, то l oop никогда не мог бы завершиться, и, поскольку переполнение со знаком является неопределенным поведением, в этом случае также свободно разрешать l oop. Поэтому самый простой и «самый эффективный» юридический кодекс заключается в том, чтобы никогда не завершаться вообще, поскольку это позволяет избежать «ненужного» сравнения и условного перехода.
Вы можете считать это либо клевым, либо извращенным, в зависимости от вашей точки зрения. .
(Для дополнительного кредита: посмотрите, что icc -O2
делает и попытайтесь объяснить это.)