winnie надыбал потрясающий пример, как из пня сделать крэй-1.
Я немножко дополировал и заретушировал его.
А теперь внимание, вопрос, где здесь чёрный ящик.
#include <iostream>
int main()
{
int x = 27;
for(int i=0; i < 10; ++i)
{
std::cout << i << " : " << i*1000000000 << " : " << x << std::endl;
if(x==1) break;
x = x%2 ? x*3+1 : x/2;
}
}
Здравствуйте, Кодт, Вы писали: К> где здесь чёрный ящик.
тут
i*1000000000 — переполнение int — UB.
Так, например, в эксперименте с gcc и с INT_MAX == 231-1, компилятор вполне разумно считает, что без переполнения i не может быть больше 2. А раз 2 < 10, то и выход по условию (i < 10) невозможен, так что его и проверять не стоит. Впрочем другие компиляторы по другому чудят.
Здравствуйте, watchmaker, Вы писали:
К>>4-байтный. W>А байт на вашей платформе какой?
Ну если это всё ещё пень, а не крэй, то 8-битный.
Да, там умножение на миллиард, для i=3 происходит целочисленное переполнение.
И?
Подчёркиваю: у меня воспроизвелось в реальных условиях, безо всякого злого колдунства, и хоть диск я успел спасти от форматирования, но эффект наблюдал занимательнейший.
Здравствуйте, watchmaker, Вы писали:
К>> где здесь чёрный ящик. W>i*1000000000 — переполнение int — UB.
Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение.
Вот за такие шутки авторов таких компиляторов бить по наглым рыжим мордам.
Ну или требовать -fwrapv в обязательные опции по умолчанию.
UPDATE[2019-06-30]: сейчас так не думаю. Считаю, что обязательно ввести в язык (оба, раз речь про C/C++):
— уровень 1: операции типа {add,sub,mul,bsl}_overflow с выставлением флага по факту переполнения; такое же для конверсии со сжатием размера; это уже даст переносимую возможность эффективно проверять такое вручную;
— уровень 2: шаблонные функции типа checked_add_with_flag, truncating_mul;
— уровень 3: синтаксические контексты для выбора характера операции, задаваемые атрибутами выражения или блока, а до конца блока, функции или входного файла — прагмами.
Обоснование: оптимизации на основании возможности компилятору предполагать, что переполнение не планировалось, таки нужны, но в очень малой доле (грубо говоря, 5%) от всего кода, а для остального (то есть по умолчанию) должны быть максимально сильные меры по отлову и немедленной генерации ошибки (исключения).
Здравствуйте, netch80, Вы писали:
N>Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение.
Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang?
N>Вот за такие шутки авторов gcc бить по наглым рыжим мордам.
Какой-то невероятно нелогичный выбор кого бить
Это же комитет сделал в этом месте UB.
А вот gcc наоборот сделал implementation defined вместо undefined, предоставив тот же -fno-strict-overflow. Ну или упомянутый -fwrapv с ещё более строгим поведением.
N>Ну или требовать -fwrapv в обязательные опции по умолчанию.
Ну это же легко исправляется. Собственно, всё равно же в makefile всё вот это нужно писать.
Уж лучше требуй чтобы -Wall включили, или чтобы -std= со стандартным языком включили по умолчанию (а не с той дикой смесью из нескольких стандартов и расширений, что активна по-умолчанию сейчас) — вот где боль.
Здравствуйте, Кодт, Вы писали:
К>Ну если это всё ещё пень, а не крэй, то 8-битный. К>Да, там умножение на миллиард, для i=3 происходит целочисленное переполнение. К>И?
К>Подчёркиваю: у меня воспроизвелось в реальных условиях, безо всякого злого колдунства, и хоть диск я успел спасти от форматирования, но эффект наблюдал занимательнейший.
Здравствуйте, watchmaker, Вы писали: W>Здравствуйте, Кодт, Вы писали: К>> где здесь чёрный ящик. W>
тут
i*1000000000 — переполнение int — UB.
W>Так, например, в эксперименте с gcc и с INT_MAX == 231-1, компилятор вполне разумно считает, что без переполнения i не может быть больше 2. А раз 2 < 10, то и выход по условию (i < 10) невозможен, так что его и проверять не стоит. Впрочем другие компиляторы по другому чудят.
Что то у меня честно проверял i<10 на каждой итерации.
Здравствуйте, netch80, Вы писали:
N>Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение.
Ну вот смотри, итерации 0, 1, 2 проходят успешно.
На итерации 3 случилась беда. Дальше может быть всё, что угодно.
Компилятору было угодно не форматировать винчестер, а крикнуть IDDQD.
N>Вот за такие шутки авторов gcc бить по наглым рыжим мордам. N>Ну или требовать -fwrapv в обязательные опции по умолчанию.
Горе от ума какое-то. Такая агрессивная преждевременная оптимизация, точно по заветам Дейкстры, вымостила дорогу в ад.
Кстати, если там добавить любое условие, — компилятор уже усомнится: а вдруг не все пути проходят через опасную формулу? И не станет вытаскивать бомбу из недр цикла в его шапку.
Здравствуйте, Marty, Вы писали:
M>А что произошло, раскрой интригу?
Компилятор обнаружил, что все пути в теле цикла проходят через i*миллиард, поэтому — если, конечно, программист не ССЗБ, — i лежит в диапазоне -2..+2, а следовательно, условие в шапке i<10 выполняется всегда. И поставил там true.