Здравствуйте, Кодт, Вы писали:
К>Причём на 32-битной машине он просто немножко потупит, когда в своп залезет,
А может и не немножко. Я вот забыл перевести миллиметры в метре, нажал на запуск, программа запросила в 1000 раз больше памяти, чем надо, система встала раком, ctrl+shift+esc не работает, потому что комбинация вызова диспетчера задач имеет тот же приоритет, что и вызов просмотрщика фотографий котиков, охеренно мудрое решение от микрософт, жму ресет, посылаю лучи ненависти микрософту.
Здравствуйте, Tilir, Вы писали:
N>>Не должно быть никакого "что угодно". Должно быть примерно следующее (и это должно быть в стандарте): в случае, если результат не помещается в целевой тип целого со знаком, компилятор и среда исполнения могут, по выбору: N>>* выполнить операцию в соответствии с правилами платформы N>>* выполнить операцию в соответствии с общепринятой арифметикой в дополнительном коде N>>* выполнить операцию, отдав в качестве результата зависящее от реализации значение N>>* по зависящим от реализации правилам выполнить действие, предусмотренное для особой ситуации типа "целочисленное переполнение"
T>Без возможности сделать на этапе компиляции arithmetical reassociations (а ваши условия их убивают) вы платите производительностью за то, чего не заказывали. Это противоречит философии C++.
Вы почему-то предполагаете, что типы значений внутренних промежуточных действий должны быть точно такие же, как в случае явно определённых автором кода операций. Это ограничение насильственно и неадекватно.
T> Убирать такие вещи под опции -- верное решение. Если вы сомневаетесь и производительность не критична -- подавайте -fwrapv и будет счастье.
Если Вы посмотрите на исходное, увидите, что там не было никакой видимой причины выставлять wrapv поведение, никакой арифметики по модулю 2**32 не предполагалось. Это как раз совершенно неожиданный удар от компилятора туда, где не предполагалось защиты.
Здравствуйте, TarasB, Вы писали:
TB>Здравствуйте, watchmaker, Вы писали:
W>>Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang?
TB>Не могу вообще понять логику компилятора.
Ааа, дошло, указатель на функцию изначально нулевой, а вызов нулевого указателя — это УБ, поэтому компилятор вправе поставить туда что угодно? Весело получается. Компилятор вправе запустить ядерную ракету, если увидел в коде УБ, а что — всё по стандарту.
Здравствуйте, watchmaker, Вы писали:
W>Здравствуйте, netch80, Вы писали:
N>>Непонятно, почему это UB вообще ломает всё, а не только одно вычисленное выражение. W>Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang?
Это из-за того, что Do не инициализирована?
Какой добрый компилятор
Здравствуйте, TarasB, Вы писали:
TB>Не могу вообще понять логику компилятора.
Компилятор видит, что есть только один способ придать этому указателю валидное значение. Поэтому, когда компилятор видит вызов, он предполагает, что программист не использует неопределённое поведение вызова нулевого указателя, а присвоил валидное значение где-то ранее; затем, следующим проходом, компилятор инлайнит функцию.
Здравствуйте, Marty, Вы писали:
W>>Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang? :)
M>Это из-за того, что Do не инициализирована?
Нет. Тем более, что в данном случае переменная Do гарантированно инициализирована перед запуском программы (значением NULL).
N>Вы почему-то предполагаете, что типы значений внутренних промежуточных действий должны быть точно такие же, как в случае явно определённых автором кода операций. Это ограничение насильственно и неадекватно.
Это совсем не банально. GCC для reassociations работает с SSA-представлением, там нет никакого различия между переменными которые программист завел лично и переменными, которые ему сделал компилятор как временные.
Мало того, я могу эквивалентно переписать этот код, добавив туда явное определение:
#include <iostream>
int main()
{
int x = 27;
for(int i=0; i < 10; ++i)
{
int t = i*1000000000;
std::cout << i << " : " << t << " : " << x << std::endl;
if(x==1) break;
x = x%2 ? x*3+1 : x/2;
}
}
Gimple для этого кода будет идентичен для приведенного в начале топика.
Если компилятор будет себя вести не идентично в первом и втором случае -- это откроет двери ада.
N>Если Вы посмотрите на исходное, увидите, что там не было никакой видимой причины выставлять wrapv поведение, никакой арифметики по модулю 2**32 не предполагалось
Умножение явно переполняющее int -- достаточно серьёзное основание.
Здравствуйте, netch80, Вы писали:
N>Что ты назвал "оптимизацией"? Такая опция, наоборот, устраняет оптимизации. Если ты её имел в виду, то используй, пожалуйста, более адекватные термины. N>Далее, с чего это тут дорога в ад?
Вообще-то он с тобой согласился
Т.е. ответ про "горе от ума" был не в противовес твоему, а в подтверждение. Фразой "дорога в ад" характеризовались разработчики компилятора, а не ты.
PS.
Все-таки, замечаю и тут, и в том числе и на себе: у программеров больное самолюбие, чуть что не так — сразу в штыки — надо с этим как-то бороться.
Здравствуйте, watchmaker, Вы писали:
W>>>Так это же суть UB. Или ты не видел тот пример с форматированием диска и clang?
M>>Это из-за того, что Do не инициализирована?
W>Нет. Тем более, что в данном случае переменная Do гарантированно инициализирована перед запуском программы (значением NULL).
Ну, я подразумевал, что она явно не инициализирована каким-то значением. То, что она инициализируется нулем, я в курсе. То, что разыменование 0 это UB, я тоже.
Беда именно поэтому происходит, или я что-то не увидел еще?
Здравствуйте, watchmaker, Вы писали:
W>Нет. Тем более, что в данном случае переменная Do гарантированно инициализирована перед запуском программы (значением NULL).
Вот именно, что она инициализирована нулём. А разыменование нуля (вызов функции по этому адресу) — неопределённое поведение.
1. Кроме -fno-strict-overflow, проблема лечится -fno-aggressive-loop-optimizations. Последний появился в 4.8 и формально включен на всех уровнях (а реально — RTFS'ить надо).
2. А вот ещё более суровый вариант:
#include <stdio.h>
int main()
{
int i, j, x = 27;
for (i=0, j=0; i < 10; ++i, ++j)
{
printf("%d : %d : %d\n", i, j*1000000000, x);
if (x==1) break;
x = x%2 ? x*3+1 : x/2;
}
return 0;
}
N>>Вы почему-то предполагаете, что типы значений внутренних промежуточных действий должны быть точно такие же, как в случае явно определённых автором кода операций. Это ограничение насильственно и неадекватно. T>Это совсем не банально. GCC для reassociations работает с SSA-представлением, там нет никакого различия между переменными которые программист завел лично и переменными, которые ему сделал компилятор как временные.
Вот это и не адекватно.
T>Мало того, я могу эквивалентно переписать этот код, добавив туда явное определение:
[...] T> int t = i*1000000000; T>Gimple для этого кода будет идентичен для приведенного в начале топика. T>Если компилятор будет себя вести не идентично в первом и втором случае -- это откроет двери ада.
В этом примере по сравнению с предыдущим я согласен, что они должны вести себя идентично — потому что в любом случае i*1000000000 написал автор кода.
А вот для сгенерированных компилятором промежуточных переменных (например, p[i] превращается в t1=p+i; *t1) это уже не так, нет никакой причины требовать от них принадлежности тем же доменам.
N>>Если Вы посмотрите на исходное, увидите, что там не было никакой видимой причины выставлять wrapv поведение, никакой арифметики по модулю 2**32 не предполагалось T>Умножение явно переполняющее int -- достаточно серьёзное основание.
Тут — да. А если константа в миллиард пришла из заголовочного файла цепочкой в 10 включений и константные вычисления — нет, недостаточно.
Здравствуйте, wander, Вы писали:
N>>Что ты назвал "оптимизацией"? Такая опция, наоборот, устраняет оптимизации. Если ты её имел в виду, то используй, пожалуйста, более адекватные термины. N>>Далее, с чего это тут дорога в ад? W>Вообще-то он с тобой согласился W>Т.е. ответ про "горе от ума" был не в противовес твоему, а в подтверждение. Фразой "дорога в ад" характеризовались разработчики компилятора, а не ты.
Ну это было совсем не очевидно.
W>PS. W>Все-таки, замечаю и тут, и в том числе и на себе: у программеров больное самолюбие, чуть что не так — сразу в штыки — надо с этим как-то бороться.
Вообще-то я не вышел за пределы просьбы уточнения в заведомо сомнительном контексте.
Здравствуйте, Кодт, Вы писали:
W>>Нет. Тем более, что в данном случае переменная Do гарантированно инициализирована перед запуском программы (значением NULL).
К>Вот именно, что она инициализирована нулём. А разыменование нуля (вызов функции по этому адресу) — неопределённое поведение.
Ну это не оправдывает "хитрый" компилятор, который видя нулевой указатель, подсовывает ему первую попавшуюся похожую функцию. Лучше бы уж честно вызывал код по адресу 0. Так бы просто SEGFAULT получился. Что за умники там этот clang и gcc пишут?
Здравствуйте, Кодт, Вы писали:
N>>Не должно быть никакого "что угодно". Должно быть примерно следующее (и это должно быть в стандарте): в случае, если результат не помещается в целевой тип целого со знаком, компилятор и среда исполнения могут, по выбору: К>То есть, поменять undefined на unspecified, а если заплатить комитету стандартизации много пожертвований, то вообще на implementation-defined (чтобы авторы компиляторов были вынуждены внести эффекты ещ1 и в свою документацию).
Ну в общем-то да. Только не заплатить, а шантажировать.
И документировать лучше за счёт включения определений, проверяемых хотя бы через #if, какая локальная политика (например, что платформа с дополнительным кодом и переполнения в явно заданных юзером операциях приводят к усечению).
К>>>Горе от ума какое-то. Такая агрессивная преждевременная оптимизация, точно по заветам Дейкстры, вымостила дорогу в ад. N>>Что ты назвал "оптимизацией"? Такая опция, наоборот, устраняет оптимизации. Если ты её имел в виду, то используй, пожалуйста, более адекватные термины. N>>Далее, с чего это тут дорога в ад? К>Оптимизация, конечно: вместо test+jnz сделал jmp
Понятно, спасибо. А в случае последнего посланного мной примера (где добавлена переменная j), очевидно, сработало в противоположную сторону.
К>Вот именно, что она инициализирована нулём. А разыменование нуля (вызов функции по этому адресу) — неопределённое поведение.
Кодт, тебя КО укусил? Что там UB было написано изначально. Прямо теряюсь, когда мне объясняют моё же пример — как автор, я то уже знаю в чём там секрет
Здравствуйте, Marty, Вы писали:
M> То, что разыменование 0 это UB, я тоже. M>Беда именно поэтому происходит, или я что-то не увидел еще?
Именно так.
M>Это тоже UB и компилятор может придумать все, что ему угодно? Или будет работать?
Используй стандартный offsetof. Ну и прочитай про границы его применимости. В принципе, на некоторых компиляторах offsetof реализован практически как и в твоём коде. Соответственно и работать, и ломаться будут они примерно в одинаковых ситуациях.
Не, это совершенно другой баг. Там проблема в том, что UB просачивается через unreachable code. Формально это означает, что компилятор находит UB там где стандарт говорит, что поведение определено. Конечно, это не связано с исходным примером, где UB есть уже по стандарту.
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, netch80, Вы писали:
N>>-O2 это не один уровень на всех, это описанный в документации набор опций, которыми можно управлять и отдельно. Данная конкретная опция называется "strict-overflow", вот описание из info:
J>о, ты уже написал про это Какой удар от классика
Да, но акценты у меня совсем другие. Компилятор не должен гнать бред только потому, что -O2.
Мне представляется, что в современных C/C++ слишком много UB не по делу.
Это следовало бы пофиксить. Т.е. разумным образом доопределить семантику языка, что бы убрать ряд дурацких UB.
В данном конкретном случае всё-таки следует доопределить свойства знаковых целочисленных типов, так что бы они реализовывали обычную 2'c арифметику.
А всякую экзотику типа 1'c, 0'c и арифметику с насыщением сделать опциональной, если она поддерживается железом, с помощью соответствующих встроенных типов
типа int_1c, int_0c, int_sat и.т.п.