Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
*(table+4) — тоже UB.volatile), но для соответствия стандарту ничего такого не требуется.Но тогда получается, что практически всё, лежащее в основе существующих в реальности систем — результат случайного стечения обстоятельств.
It is undefined behavior because it is not defined what the behavior is.
(This deserves emphasis because many programmers think «undefined» means «random», or «unpredictable». It doesn't; it means not defined by the standard. The behavior could be 100% consistent, and still be undefined.)
Could it have been defined behavior? Yes. Was it defined? No. Hence, it is «undefined».
That said, «undefined» doesn't mean that a compiler will format your hard drive...it means that it could and it would still be a standards-compliant compiler.
именно то, что желает вычислить программистКомпилятор не читает мысли, а программы делают то, что им сказано, а не то, что подразумевалось. Особенно это касается Си, одно из главных правил которого: не мешать программисту делать то, что он хочет. А додумывающие компиляторы, бережно вставляющие проверочки на каждый чих, пусть остаются для безопасных managed языков. Или можно использовать языки, которые лучше подходят для написания «достаточно умных компиляторов», чтобы те были способны запретить UB синтаксически (ценой невозможности кастовать слонов в комоды и писать куда попало в память, даже если очень надо).
struct some_struct
{
// ...
type flexible_array[];
};
Если бы программист хотел написать return true вместо цикла — он бы так и написал.Вы так говорите, как будто никогда не смотрели внутрь Boost'а или libstcd++…
true.true. while (a!= MAX && b!=MAX && с != MAX) {
есть реальность (организация адресного пространства и прочее), которая никуда не собирается исчезать
memcpy/memmove в glibc (это GNU реализация стандартной библиоеки языка С).memcpy как алиас на memmove. Это не было никаким нарушением. И вот несколько лет назад авторы решили зделать новую, оптимизированную реализацию memcpy. Эта новая реализация тоже соответствовала стандартам. memcpy пересекающиеся регионы. А что такого, работало же!memcpy.В результате из-та таких програмистов пришлось откатить оптимизираванную версию memcpy.
А как же тогда написать функцию memmove? Она же должна сравнивать указатели, чтобы понять, перекрываются ли блоки памяти и каким образом.
В том то и проблема, что компилятор НЕ ВИДИТ и не отличает одну ситуацию от другой.
…moreover, if the expression P points to the last element of an array object, the expression (P)+1 points one past the last element of the array object, and if the expression Q points one past the last element of an array object, the expression (Q)-1 points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.
В данном конкретном случае — может быть.Повторюсь — если бы не видел, то он не имел бы права сделать свою агресивную оптимизацию.
for (int i = 0; i < 42; ++i)
{
do_something(i);
}
++i возможно UB. Хотим ли мы, чтобы компилятор вставил проверку или выдавал предупреждение? Ведь тут нет никакого UB, т. к. i никогда не достигнет INT_MAX. do_something получает i по ссылке и изменяет его значение? Как компилятор может это понять? Только в процессе преобразований кода, то есть в процессе оптимизации.На самом деле есть четвертый вариант:
4) Формально UB в некоторой конструкции возможно, но логика программы такова, что оно никогда не реализуется.
возникает UB или нет в каждом из этих случаев, но он просто не тратит на это время
for (int i = 0; i < 42; ++i)
{
do_something(i);
if(i==INT_MAX) can_eliminate_this_call();
}
i в операторе ++i принимает значение INT_MAX, то результат операции допускается любой».do_something() присвоить i значение INT_MAX, прежде чем удалить вызов can_eliminate_this_call()».i принимает значение INT_MAX, то я могу удалить вызов can_eliminate_this_call(), потому что UB, а если не принимает, то я могу удалить этот вызов, потому что условие ложно. Даже заглядывать внутрь do_something() незачем!»да чего мне париться? если i принимает значение INT_MAX, то я могу удалить вызов can_eliminate_this_call(), потому что UB, а если не принимает, то я могу удалить этот вызов, потому что условие ложно
Но фактически очень сомнительно, что именно этого хотел программист
Но для того, чтобы получить свободу в реализации неопределенного поведения, компилятор сначала должен доказать, что неопределенное поведение имеет место.Никому он ничего не должен. Простейший пример: «1 << 129». В «железе» это будет 2 на x86 и 0 на ARM'е. И что с этим значением должен делать компилятор? А кросс-компилятор? А если это явным образом
costexpr функция? Да, можно доопределить всё тем или иным способом. Можно вввести 100500 «правил игры». А можно просто сказать: это не задача компилятора и программист должен как-то сделать так, что никому не будет интересно чему равно «1 << 129». Просто сделать так, чтобы этого не случалось.char *a=0x1234;
free(a);
free вы вполне можете передать в функцию NULL и всё будет законно. Потому что так говорит стандарт C++. А в программах для POSIX и/или Win32-систем вы можете-таки сравнивать указатели на разные массивы (так как этими стандартами гарантируются плоское адресное пространство), а вот как раз в DOS (скажем в Large модели) у вас вполне могут быть разные указатели, которые физически указывают на один участок памяти. И там разного рода трюки реально могут приводить к известного рода проблемам.это не задача компилятора и программист должен как-то сделать так, что никому не будет интересно чему равно «1 << 129». Просто сделать так, чтобы этого не случалось
а вот как раз в DOS (скажем в Large модели) у вас вполне могут быть разные указатели, которые физически указывают на один участок памяти. И там разного рода трюки реально могут приводить к известного рода проблемам.
Никто не говорит, что «доопределение» стандарта — это плохо. Нет, это вполне нормально. Что плохо — так это требовать от компилятора чтобы он «угадывал» что вы там имеете в виду. Вот это да — это качмар. Это — дорога в ад
В случае 2) компилятору следует честно скомпилировать программу исходя из того поведения, которое было доопределено для данной конструкции сверх стандарта, исходя из здравого смысла.
Означает ли это, что вы предлагаете, что компилятор должен пытаться угадать, что хотел получить программист
Можно в этом случае сравнить числа-указатели и вернуть результат сравнения, каким бы он ни был; а можно вернуть always-true и за счет этого сократить в программе ряд ветвлений и циклов. Как следует поступить?
Warning: unrelated pointers comparision at line 156: a pointer based upon to a[] is compared to a pointer based upon b[]
Если компилятор установит, что переполнение обязательно произойдет (оба аргумента известны на этапе компиляции) — что он должен делать? Есть вариант — сгенерировать ту же ассемблерную команду сложения, которая исполнится и вернет какой-то результат. Другой вариант — «оптимизировать», удалить команду сложения вообще. Я считаю, что здравому смыслу соответстувует первый вариант.Ok. Принято.
Если выполняется сдвиг влево 32-битного числа на 32 и более бит — следует вернуть нуль.Хотя процессор-то сделает совсем не так. «В железе» «1 << 129» будет равна 2 на x86 и 0 на ARM'е, а вот «1 << 257» будет уже равна 2 и там и там. И как должен себя вести виндовый кросс-компилятор для платформы Android? Чему в нём должен быть результат работы выражения «1 << 129»?
Означает ли это, что вы предлагаете, что компилятор должен пытаться угадать, что хотел получить программистОднако.
Столкнувшись с неопределенным поведением, компилятор в любом случае делает какие-то предположения о том, что же на самом деле хотел получить программист.Не совсем так. Он получает некоторую информацию о том, чего никогда и ни при каких условиях не может быть во время исполнения. Ну и, разумеется, он начинает этой информацией пользоваться для оптимизаций. А почему нет?
Он получает некоторую информацию о том, чего никогда и ни при каких условиях не может быть во время исполнения. Ну и, разумеется, он начинает этой информацией пользоваться для оптимизаций. А почему нет?
Хотя процессор-то сделает совсем не так. «В железе» «1 << 129» будет равна 2 на x86
И как должен себя вести виндовый кросс-компилятор для платформы Android? Чему в нём должен быть результат работы выражения «1 << 129»?
всем им надо отрывать руки по плечи в выгонять вон из профессии
y = (x << 5) | (x >> 27);
Насколько я знаю, большинство кода для встраиваемых систем сегодня компилируется на gcc
А на ПК, оптимизирующие компиляторы существуют уже полтора десятка лет
uint8_t add_oflow(uint8_t a, uint8_t b)
{
uint16_t c = (uint16_t) a + (uint16_t)b;
return (uint8_t)(c & 0xFF);
}
избегает неопределенного поведения, при этом эмулируя переполнение типа Wrap-aroundНо в любом случае надеюсь, что вы меня поняли, что я поддерживаю те варианты доопределения случаев UB, которые максимально соответствуют результатам выполнения этих запрещенных операций в железе.Я очень рад за вас и надеюсь посмотреть на ваш новый язык когда вы его сотворите. Но это не будет языком C и/или C++!
В этой ситуации программист, пишущий код, который заведомо приводит к целочисленным переполнениям, сознательно ограничивает работоспособность своего кода только целевой платформой.Нет, в этом случае создаёт себе проблему в виде программы, которая не является программой на языках C и/или C++. Что и как это чудо будет делать разработчиков компилятора не интересует. Причём не интересует от слова «совсем».
Вы скажете, что такие случаи необходимо документировать. Да, документировать можно. Но не обязательно, так как они соответствует здравому смыслу, а именно — свойствам целевой платформы. Даже не читая документацию, разумно ожидать от компилятора именно такого поведения.Нет, нет и нет. В тех местах, где вы используете «implementation-defined behavior» вы вправе ожидать того, о чём вы написали. На места же, где вы натыкаетесь «undefined behavior» вы наступать не имеете права. Совсем. Никогда. Наступили? ССЗБ — пойдите и исправьте ошибку. В вашей программе, разумеется, не в компиляторе.
Допустим, ваш компилятор жестоко наказывает вас за каждое целочисленное переполнение.Ну зачем же за каждое то наказывать? Это случается только тогда, когда это может сделать вашу программу быстрее. Ну там извести проверку на окончание цикла можно или ещё чего.
Результат работы вашей программы при переполнениях становится совершенно непредсказуемым, что вынуждает вас переделать ее таким образом, чтобы избежать переполнений любой ценой.Такую и только такую программу вы можете компилировать без указания флага
-fwrapvИ что получится в результате? Ваша программа обрастет огромным количеством проверок. И вы не сможете воспользоваться поведением процессора при переполнениях. Вам придется эмулировать это поведение сложными конструкциями, которые будут медленно работать. Скорее всего придется повысить разрядность используемых чисел сверх необходимого. А это — расход ценных регистров процессора.Ну что ж поделать. Но если у вашего компилятора нет флага, аналогичного, GGC'шному
-fwrapv, то у вас нет выбора. Если у вас есть код, который вызывает в некоторых условиях UB, то у вас нет программы на языке C или C++. То, что у вас есть — это бомба замедленного действия, которая грозит взорваться при выходе новой версии компилятора. Потому что компиляторы научаются пользоваться тем фактом, что программа на языке C или C++ никогда и ни при каких условиях не вызывает UB всё лучше и лучше.Не забывайте, что язык C разрабатывался для низкоуровнего применения, поэтому существует много кода на C, непортируемого в принципе, но при этом прекрасно обслуживающего какую-то аппаратную платформу.То что вы хотите использовать бензопилу для забивания гвоздей — ваши проблемы. Язык C разрабатывался низкоуровневых переносимых программ, прежде всего. И потому никакие программы на языке C никогда не должны вызывать UB — иначе они будут непереносимы и это лишит затею всякого смысла! Если же вы хотите писать программу под конкретную платформу — ну бог вам судья, делайте что хотите, но разработчики C и компиляторов C тут вообще причём?
Неопределённое поведение и теорема Ферма