LINUX.ORG.RU

Пример выпиливания кода или необычного поведения программы из-за undefined behaviour

 ,


4

5

Привет. Я читал несколько статей о том, что undefined behaviour это страшно, что его нельзя игнорировать, потому что оптимизации компиляторов используют undefined behaviour и могут из-за него удалить часть кода, потому что она типа unreachable или что-то типа того. Короче я это в теории знаю, но не знаю сильно подробностей - хотелось бы примеры, как именно undefined behaviour приводит к тому, что оптимизации компилятора делают так, что код делает не то, что днище-программисту кажется, что он должен делать. Буду эти примеры в интернет пояснениях всяких undefined behaviour ситуаций показывать.

Еще мне интересно, считают ли компиляторы signed integer overflow за undefined behaviour и оптимизируют ли программу, учитывая тот факт, что это запрещено? Мне кажется, было бы наиболее разумно им делать вид, что это поведение вполне себе определено, и не выпиливать из-за этого код, потому что по-моему 90% C++ программистов, да и C программистов тоже не знают о том, что это UB.

Почитай приколы про memcpy. А ведь куча лохов не обращала внимания на предупреждение в манах! И напоролись!!!

Eddy_Em ☆☆☆☆☆
()

Да тот же битовый сдвиг на отрицательную величину (либо на величину, превышающую количество разрядов типа). Если я правильно помню, gcc и clang дают разные результаты.

m0rph ★★★★★
()
Ответ на: комментарий от m0rph

Не, ну это понятно, это фиг с ним. Вот мне бы такой пример, когда компилятор смотрит код «ага, тут определенно случится UB, значит будем считать, что код дальше этого UB никогда не будет достигнут, значит можно его выкинуть» или что-то подобное, а не просто какую-то штуку, которая в разных компиляторах по-разному работает.

hlebushek ★★
() автор топика
Ответ на: комментарий от hlebushek

Гугли «memcpy overlapped memory»

Было что-то про мелкомягких дебилов и еще каких-то быдлокодеров, у которых «серьезный» софт грохнулся по причине невнимательного чтения манов.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от hlebushek

На тебе еще хрень:

++a += a++ + ++a;
b = --b - b--;
if(!a || !*a);

Ну, а насчет «оптимизаций» — попробуй сделать пустой цикл для небольшой паузы на мелкоконтроллере, или же запили что-нибудь вроде

i = 0;
while(!i);
когда у тебя i изменяется в прерывании, не указав volatile...

Ну и т.д., и т.п.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от amaora

Здесь вряд ли UB, т.к. все компиляторы инициализируют глобальные переменные нулями.

Eddy_Em ☆☆☆☆☆
()

[offtopic]
Ура! На ЛОРе нормально заработала фильтрация тегов: черный список теперь в приоритете!!! В трекере эта тема не висит ☺
[/offtopic]

Eddy_Em ☆☆☆☆☆
()
Ответ на: например от hope13

Спасибо за первую ссылку. Я смог сделать пример с signed integer overflow, который мне нравится, который на разных уровнях оптимизации выдает разное.

Соответственно если этот number < number + 1 добавить в if, то вероятно можно сделать так, чтобы компилятор выпилил код ненужный, и на разных уровнях оптимизации выполнялись разные ветки кода.

// ========== main.cpp ==========

#include <iostream>

extern signed int number;

int main()
{
    std::cout << "number = " << number << std::endl;
    std::cout << "number + 1 = " << number + 1 << std::endl;
    std::cout << "number < number + 1 = " << (number < number + 1) << std::endl;
    return 0;
}



// ========== library.cpp ==========
#include <limits>

signed int number = std::numeric_limits<int>::max();

Компилятор Desktop Qt 5.5.0 MinGW 32bit

Результат без оптимизаций:

number = 2147483647
number + 1 = -2147483648
number < number + 1 = 0

-O3:

number = 2147483647
number + 1 = -2147483648
number < number + 1 = 1

hlebushek ★★
() автор топика
Последнее исправление: hlebushek (всего исправлений: 1)

Еще мне интересно, считают ли компиляторы signed integer overflow за undefined behaviour и оптимизируют ли программу, учитывая тот факт, что это запрещено?

По стандарту это UB, но это широко используется и не является UB с точки зрения конкретных компиляторов.

Цитата:

5.0.4 If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined. [ Note: most existing implementations of C++ ignore integer overflows. ...]

UB это является из-за того, что конкретные архитектуры могут высырать исключения на такой случай, а добавлять код их обработки было бы накладно. Но такие архитектуры еще поискать нужно.

Стандарту вообще такое бы стоило описать как unspecified behavior.

Pavval ★★★★★
()
Ответ на: комментарий от Pavval

А что такое вообще unspecified behaviour в C и C++? Пару раз этот термин рандомно в обсуждениях слышал, но ничего авторитетного и с подробностями не читал.

hlebushek ★★
() автор топика
Ответ на: комментарий от hlebushek

unspecified behavior

behavior, for a well-formed program construct and correct data, that depends on the implementation [ Note: The implementation is not required to document which behavior occurs. The range of possible behaviors is usually delineated by this International Standard. —end note ]

Pavval ★★★★★
()
Ответ на: unspecified behavior от Pavval

implementation-defined behavior

behavior, for a well-formed program construct and correct data, that depends on the implementation and that each implementation documents

Pavval ★★★★★
()

Например используется закрытый ключ, хранящийся в массива и в конце этот массив обнуляется, чтобы в памяти этот закрытый ключ не остался. А компилятор видит — код бесполезный (с его точки зрения) и выкидывает его.

Legioner ★★★★★
()

Насколько помню, тут хорошо писали. Пока искал эту заметку, нашёл ещё две; первую не помню, а вторая хорошо иллюстрирует неожиданное поведение программы.

xaizek ★★★★★
()

Еще мне интересно, считают ли компиляторы signed integer overflow за undefined behaviour и оптимизируют ли программу, учитывая тот факт, что это запрещено? Мне кажется, было бы наиболее разумно им делать вид, что это поведение вполне себе определено, и не выпиливать из-за этого код, потому что по-моему 90% C++ программистов, да и C программистов тоже не знают о том, что это UB.

Компиляторы не считают что-то за undefined behaviour, это делает стандарт. А вот компиляторы в случае, когда код подразумевает undefined behaviour, просто ведут себя так, как им удобно. Могут предупреждение/ошибку выдать, но чаще просто делают то же, что обычно в «подобной» ситуации, не задумываясь о последствиях. В случае с signed integer, например, просто генерируют тот же бинарный код. А UB заключается в том, что на одной архитектуре переполнение приведет к одному значению в переменной, а на другой - к другому, или вообще к ошибке.

asaw ★★★★★
()

Досконально не помню пример, но был случай, когда было написанно что то типа:

parse(pointer++, pointer);

Вроде даже в рамках одного компиля сиё проявлялось. Ток обьект был чуть посложнее по семантике, чем просто указатель. А функа выводилась из шаблона и была inline.

pon4ik ★★★★★
()
Ответ на: комментарий от hlebushek

Почитай стандарт, там суховато, но зато формально описанно, если не осиливаешь - почитай Алёну.

pon4ik ★★★★★
()
% cat 1.cc
#include <stdio.h>

int foo() {
	printf("foo\n");
}

int bar() {
	printf("bar\n");
	return 0;
}

int main() {
	foo();
}
% clang++ -O3 1.cc         
1.cc:5:1: warning: control reaches end of non-void function [-Wreturn-type]
}
^
1 warning generated.
% ./a.out 
foo
bar
[1]    64331 segmentation fault  ./a.out
slovazap ★★★★★
()
Ответ на: комментарий от amaora

Где ты видишь ub? В обоих твоих примерах его нет.

anonymous
()

по-моему 90% C++ программистов, да и C программистов тоже не знают о том, что это UB

Программисты, знающие все случаи UB - это вообще вымирающий вид. Очень уж веселый язык си, про плюсы можно и не говорить.

По сабжу - ищи статьи PVS Studio, adobe memcpy bug, strict aliasing break, NULL dereference optimization.

anonymous
()
Ответ на: Это победа от anonymous

Это победа

Да, пока это самое эпичное что я видел. Не понимаю почему им не сделать -Werror=return-type по умолчанию, потому что это чревато чем угодно. Но таки да, по стандарту они правы на 100%.

Падает даже без оптимизаций. Но по-другому.

Без оптимизаций оно вставляет ud2, с оптимизациями просто проваливается в следующую функцию. На самом деле раньше (наверное на clang 3.4, сейчас 3.6) оно с оптимизациями даже и не падало.

slovazap ★★★★★
()
Последнее исправление: slovazap (всего исправлений: 1)

хотелось бы примеры, как именно undefined behaviour приводит к тому, что оптимизации компилятора делают так, что код делает не то, что днище-программисту кажется, что он должен делать

http://edunow.su/site/content/cpp/oproverzhenie-teoremy-ferma-i-ub

http://habrahabr.ru/post/229963/

int table[4];
bool exists_in_table(int v)
{
    for (int i = 0; i <= 4; i++) {
        if (table[i] == v) return true;
    }
    return false;
}

int main()
{
    table[0] = 0;
    table[1] = 1;
    table[2] = 2;
    table[3] = 3;

    cout << "10 in table " << exists_in_table(10);
}

Может вернуть истину при оптимизации, хотя 10 в массиве нет.

monk ★★★★★
()

Ещё красивый пример:

struct timeval tv;
unsigned long junk;
 
gettimeofday(&tv, NULL);
srandom((getpid() << 16) ^ tv.tv_sec ^ tv.tv_usec ^ junk);

clang превращает в

struct timeval tv;
unsigned long junk;
 
gettimeofday(&tv, NULL);
getpid();
srandom(0);
Так как результат вычисления с неинициализированной переменной — UB. В результате «генератор случайных чисел» становится совсем неслучайным.

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)

Или такой прикол:

#include <stdio.h>
#include <stdlib.h>

int main() {
int *p = (int*)malloc(sizeof(int));
int *q = (int*)realloc(p, sizeof(int));
*p = 1;
*q = 2;
if (p == q)
   printf("%d %dn", *p, *q);
}



$ clang -O realloc.c ; ./a.out 
1 2

два значения по одному адресу.

monk ★★★★★
()

Вот ещё замечательная оптимизация:

enum {N=32};
int a[N], pfx[N];
void prefix_sum()
{
  int i, accum;
  for (i=0, accum=a[0]; i < N; i++, accum+=a[i])
    pfx[i] = accum;
}

gcc-4.8.0 делает из этого бесконечный цикл. И вполне имеет право выкинуть проверку на i < N, так как иначе UB.

monk ★★★★★
()
Ответ на: комментарий от anonymous

А ты на 100% уверен, что на сферической в вакууме машине компилятор не проверит сначала второе условие?

Eddy_Em ☆☆☆☆☆
()

хотелось бы примеры, как именно undefined behaviour приводит к тому, что оптимизации компилятора делают так, что код делает не то, что днище-программисту кажется, что он должен делать.

int a = 0;
int b = a++ + ++a;
andreyu ★★★★★
()
Ответ на: комментарий от Eddy_Em

шланг всего лишь «строго соответствует стандарту». А вот кому в голову пришла мысль в стандарт вводить понятие UB как разрешения делать ЧТО УГОДНО — это вопрос.

monk ★★★★★
()
Ответ на: комментарий от Eddy_Em

на 100% уверен, что на сферической в вакууме машине компилятор не проверит сначала второе условие

Да. В стандарте прописан жёсткий порядок для || и &&

monk ★★★★★
()
Ответ на: комментарий от Eddy_Em

Для стандарта С++ 2011 пункт 5.15 Logical OR operator: ... Unlike |, || guarantees left-to-right evaluation; moreover, the second operand is not evaluated if the first operand evaluates to true.

monk ★★★★★
()
Ответ на: комментарий от Eddy_Em

Нафиг нужен компилятор, поведение которого невозможно предсказать?

На самом деле вопрос звучит так: «Нафиг нужен язык, поведение программы на котором невозможно предсказать?». Компиляторы всего лишь соответствуют стандарту языка.

monk ★★★★★
()
Ответ на: комментарий от monk

Языку С уже дофига лет! Он уделал асм, форт, фортран, паскаль, аду, …

И до сих пор никто не придумал язык лучше С!!! Деннис Ритчи — воистину БОХ!

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

Он уделал асм,

никогда не умрет

форт,

помощь зала!

фортран,

но-но! бласы с лапаками используешь, небось?

паскаль, аду, …

виртоидеи повлияли на жаву

AptGet ★★★
()
Ответ на: комментарий от Eddy_Em

Я о нормальном С говорю!

ОК, В ISO/IEC 9899:TC2 в пункте 6.5.14 написано «Unlike the bitwise | operator, the || operator guarantees left-to-right evaluation; there is a sequence point after the evaluation of the first operand. If the first operand compares unequal to 0, the second operand is not evaluated.»

monk ★★★★★
()
Ответ на: комментарий от Eddy_Em

Языку С уже дофига лет!

И все эти годы проблемы переносимости решаются исключительно пачками ifdef'ов. И при переходе на другой компилятор или ОС всегда надо заново прогонять все тесты (и надеяться, что из достаточно).

monk ★★★★★
()
Ответ на: комментарий от AptGet

никогда не умрет

Только из-за «железячных» особенностей.

бласы с лапаками используешь, небось?

Они уже давным-давно на С переписаны!

виртоидеи повлияли на жаву

Лучше бы этой жабки не было никогда!

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от monk

А ты уверен, что какие-нибудь быдлокодеры из мелкомягкой конторы (или нетрадиционные лица из огрызка) внимательно читали стандарты при создании компилятора под свой шлак? Мне-то пофиг, но ...

Eddy_Em ☆☆☆☆☆
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.