ШОКИРУЮЩАЯ ПРАВДА про undefined behavior в C и С++!

bleyman

Многие полагают, что undefined behavior в си и плюсах должен заботить исключительно людей, которые пишут под экзотические платформы, в которых например отрицательные числа представляются не как two's complement. Это не так!
http://ideone.com/yo0uP

#include <stdio.h>
 
int f(int value)
{
    int my_cup = value + 10;
    if (my_cup < value)
    {
     printf("... overfloweth!\n");
    }
    return my_cup;
}
 
int main(int argc, const char * argv[])
{
    printf("%d\n", f(argc + 0x7FFFFFF8;
    return 0;
}


prog.c:6: warning: assuming signed overflow does not occur when assuming that (X + c) < X is always false
output:
-2147483645

Оно не печатает "... overfloweth!"! Вообще! Компилятор соптимизировал этот код нафиг! Тот, который используется в ideone, хотя бы варнинг выдал, более старые версии GCC и новый clang делают то же самое и даже варнинга не дают. MSVC 2010 Express не применяет такую оптимизацию, не знаю, потому ли что Express, или потому что Микрософт более трепетно относится к плохому но нужному кому-то коду.
Ну собственно и вот: следует чётко понимать, что в какой-то момент достаточно давно undefined behavior перестал быть про написание кроссплатформенного кода, и начал восприниматься как неявное указание компилятору на возможности оптимизации: ты, как программист, гарантируешь, что по имеющейся у тебя (но недоступной компилятору непосредственно) информации, ситуация, в которой undefined behavior может проявиться, на самом деле невозможна. Как если бы ты использовал прикольный __assume интринсик MSVC. Поэтому каждому, кто программирует на C или C++ нужно точно знать, что там является undefined behavior.
Я, кстати, ровно такой баг видел в реальности. Ещё был прекрасный эксплойт в Линуксе: http://isc.sans.edu/diary.html?storyid=6820 — по сути то же самое, сначала инициализируем Sometype something = ptr->field, потом на всякий случай проверяем, не нулл ли ptr, однако же во-первых, компилятор эту проверку тупо выкидывает, во-вторых, можно заммапить нулевую страницу чтобы вместо сегфолта кернел прочитал оттуда твои данные.

Devid

В MSVC2008 Team что-то не получилось.

oliver11

На тему сабжа есть прекрасная статья:
http://blog.llvm.org/2011/05/what-every-c-programmer-should-...

bleyman

Значит это не ограничение Express Edition, а стратегия Микрософта, спасибо за то, что проверил!
В gcc3.4 уже работает (то есть не работает как кажется что должно работать). Да, ты оптимизацию-то включил на максимум, надеюсь?

ppplva

Ну вот же каноническая ссылка аж 2007 года:
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=33498

elenangel

 

~/test $ g++ -O3 undef_behavior.cpp -o undef_behavior
~/test $ ./undef_behavior
-2147483645
~/test $ g++ -O2 undef_behavior.cpp -o undef_behavior
~/test $ ./undef_behavior
-2147483645
~/test $ g++ -O1 undef_behavior.cpp -o undef_behavior
~/test $ ./undef_behavior
... overfloweth!
-2147483645
~/test $ g++ -O0 undef_behavior.cpp -o undef_behavior
~/test $ ./undef_behavior
... overfloweth!
-2147483645

~/test $ g++ --version
g++ (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Serab

там в манах есть точный список ключей, которые подразумеваются под -O{0,1,2,3}, так что можно просто найти нужный ключ, а не показывать результаты при разных -Ox.
Возможно, это -fstrict-overflow

Serab

:ooo: :aaa:

tokuchu

там в манах есть точный список ключей, которые подразумеваются под -O{0,1,2,3}, так что можно просто найти нужный ключ, а не показывать результаты при разных -Ox.
Возможно, это -fstrict-overflow
Я тут одну штуку компилировал, которая обладала одним странным свойством — начиная с -O1 собиралась нормально, а с -O0 выдавала ошибку линковки. Попробовал указать все флаги, описанные в мане для -O1 — не помогло. :(

procenkotanya

> Попробовал указать все флаги, описанные в мане для -O1 — не помогло.
Оптимизации не работают при -O0, даже если пытаться включать их отдельно, поэтому нужно, наоборот, ставить -O1 и выключать оптимизации.
И ещё, документация не всегда согласована с текущим положением дел, поэтому лучше включенные оптимизации смотреть через gcc -Q --help=optimizers или с помощью -fverbose-asm (а-ля echo | gcc -xc - -o- -S -O -fverbose-asm | sed -e 's/ /\n/g' | grep ^-[mf])

ppplva

dead code elimination часто к такому приводит. Т.е. у тебя где-то в мертвом коде используется несуществующий символ, ссылку на который оптимизатор выкидывает вместе с кодом.

tokuchu

Оптимизации не работают при -O0, даже если пытаться включать их отдельно, поэтому нужно, наоборот, ставить -O1 и выключать оптимизации.
Т.е. если я напишу "gcc -fmerge-constants file.c" это будет всё равно что без флагов? Как то странно. Не знал об этом. А выключаются они через -fno-*?

tokuchu

поэтому лучше включенные оптимизации смотреть через gcc -Q --help=optimizers
Ну вот, кстати, сделал "gcc -O0 -Q --help=optimizers" и для -O1 тоже. Для -O0 уже какие-то флаги выставлены всё равно. Пробовал сделать -O1 с отключением флагов до уровня -O0 — всё равно собирается. Пробовал отключать флаги дальше, в результате стали проявляться какие-то другие ошибки местами и похоже этот как-то связано с тем, что оптимизации как-то зависят друг от друга. Т.к. например была ругань на то, что open надо вызывать только с 2 или 3 аргументами, и если там O_CREAT, то с 3 обязательно. Там, где ругалось был вызов open с 2 аргументами и без O_CREAT. В общем мне всё же удалось как-то наотключать их так, что осталось только "-ftree-forwprop", с выключением которого configure вообще говорил, что "компилятор не может экзешники делать вообще". Но даже так собирается всё равно. Видно есть в -O1 ещё какая-то магия. :)

procenkotanya

Видно есть в -O1 ещё какая-то магия.
Да, некоторые оптимизации не имеют собственного -f-флага и включаются просто при -O > 0. Это есть в документации, но не на самом видном месте (и кто ж её читает, в любом случае:)

Not all optimizations are controlled directly by a flag. Only optimizations that have a flag are listed in this section.
Most optimizations are only enabled if an -O level is set on the command line. Otherwise they are disabled, even if individual optimization flags are specified.

tokuchu

Спасибо.

ax222

хм. вот для примера немножко других языков:

bleyman

Ну это неудивительно, откуда в Скале undefined behavior? Из известных мне языков он только в C & C++ есть, как таковой.

ax222

Ну да, просто стало интересно, вдруг внезапно тоже if выкинет)
В яве в принципе тоже есть какие-то compile-time оптимизации. Например, если метод возвращает константу - выкинуть вызов метода и везде заинлайнить константу, или если приватный метод не вызывается нигде в классе - не компилировать его вообще (на Reflection пох
Но с этим if-ом во всех случаях все хорошо вроде.

serega1604

>Например, если метод возвращает константу - выкинуть вызов метода и везде заинлайнить константу
такое нельзя делать для non-final non-static non-private методов, поэтому поставим твое утверждение под сомнение.

ax222

Да, я не то написал. Инлайнятся public static final константы.
Это конечно не undefined behaviour, но те кто не в курсе, могут столкнуться с подводными камнями, если, например, константы определены где-то в отдельной библиотеке, и они решат только ее перекомпилировать)

Maximilian

php затуманил мой разум ...
сорри, не по делу

bleyman

Да ты же упоротый! Смотрите, пацаны, он упоротый!

Ivan8209

> Многие полагают, что undefined behavior в си и плюсах должен
> заботить исключительно людей, которые пишут под экзотические
> платформы, в которых например отрицательные числа
> представляются не как two's complement. Это не так!
Как раз недавно выправлял кучу кода, который, по задумке,
должен уметь более или менее правильно отрабатывать случаи,
когда упирается в недостаток памяти и прочие неожиданности.
Узнал много нового.
> Оно не печатает "... overfloweth!"! Вообще!
> Компилятор соптимизировал этот код нафиг!
Ты даже не поверишь, насколько хорошо, что это так!
Потому что противоположный случай, когда сравнения выполняются с
неожиданным исходом (a<b, a+d<b, но на самом деле точка b внутри
отрезка многие не воспринимают, а оно там живёт и плодится.
То, что компиляторы научились оптимизировать такие случаи,
вселяет надежду, что в будущем не придётся выполнять работу ATP
вручную.
У меня вообще возникла мысль, что в сях надо было сделать так,
как поступили в оккаме: сказать, что арифметические действия
выполняются в конечном кольце, и устранить предикаты якобы
порядка. (Либо, если оставлять порядок, гарантировать насыщение,
а не допускать взятие остатка.)
---
"Значение болевого импульса в нашей жизни неоценимо!"

Maximilian

У меня вообще возникла мысль, что в сях надо было сделать так,как поступили в оккаме: сказать, что арифметические действиявыполняются в конечном кольце, и устранить предикаты якобыпорядка. (Либо, если оставлять порядок, гарантировать насыщение,а не допускать взятие остатка.)
http://en.wikipedia.org/wiki/Saturation_arithmetic
http://gcc.gnu.org/wiki/FixedPointArithmetic

Maximilian

Я тут одну штуку компилировал, которая обладала одним странным свойством — начиная с -O1 собиралась нормально, а с -O0 выдавала ошибку линковки. Попробовал указать все флаги, описанные в мане для -O1 — не помогло.
ещё надо -O пустое ставить
Возможно, это -fstrict-overflow
 C:\prog>(g++ -O  undef_behavior.cpp -o undef_behavior.exe   ) && ( undef_behavior.exe)
... overfloweth!
-2147483645
C:\prog>(g++ -O -fstrict-overflow undef_behavior.cpp -o undef_behavior.exe ) && ( undef_behavior.exe)
-2147483645

tokuchu

ещё надо -O пустое ставить
Так вроде в мане написано, что это то же самое, что и -O1. Да и на деле так выходит.
Оставить комментарий
Имя или ник:
Комментарий: