Страницы

Поиск по вопросам

пятница, 29 ноября 2019 г.

Понять где undefined behavour в арифметических выражениях

#c++ #c #c++11 #неопределенное_поведение


Довольно таки часто обсуждаемая тема, но тем не менее хотелось бы конкретнее разобраться,
где есть UB, а где нет.

Ниже несколько примеров, и мои мысли по поводу что есть что:

int i = 0, x = 1;
int a[6] = {0, 0, 0, 0, 0, 0};

i = ++i + ++i; // UB
i = i++ + ++i; // UB
x = i++ + ++i; // ?? я думаю что UB 
x = i++ + i++; // OK
a[i] += i++;   // OK ??
a[++i] = i++;  // UB ??
a[++i] = ++i   // UB
i += ++i;      // UB
j += j;        // OK


За всё время я уяснил одно правило - если в одном выражении значение объекта меняется
более одного раза - UB чистой воды, то есть понятно, что на каких-то платформах может
и будет происходит то, что ожидается, но вопрос в том, что стандарт в таких случаях
ничего не гарантирует - то есть либо говорится, что это undefined behavour, unspecified
behavour либо implementation defined behavour. 

Вопрос - могут ли возникнуть в этих примерах случаи, кроме UB?
    


Ответы

Ответ 1



Языки С и С++ фундаментально отличаются в одной важной детали: язык С++ старается максимально тщательно сохранять lvalue-ность результата выражения (lvalue-preserving language), а язык С наоборот - в большинстве случаев сразу беззаботно теряет lvalue-ность результата выражения (lvalue-discarding language) int a = 0, b = 1; a = b; // lvalue в C++, rvalue в С ++b; // lvalue в C++, rvalue в С 1 ? a : b; // lvalue в C++, rvalue в С (a, b); // lvalue в C++, rvalue в С Эти свойства данных языков диктуют серьезные различия в их подходу к упорядочению (sequencing) операций в выражениях. Первый стандарт С++ (С++98) пытался игнорировать этот момент и придерживаться унаследованного напрямую из С подхода к упорядочению, но в конечном итоге эта модель была признана дефектной и существенно переработана. В процессе этой переработки в С++ появилась упорядоченность, которой ранее не было. Как следствие, некоторые выражения, которые формально порождали UB в C++98, получили вполне определенное поведение в C++11. А C++17 добавил еще больше отношений упорядочения в язык С++, тем самым еще более расширив круг выражений, поведение которых определено. Поэтому ответ на вопрос о наличии UB в выражении может существенно отличаться между С и С++. Например, выражение i = ++i; порождает UB в С, но имеет совершенно определенное поведение в С++. Различие возникает по той причине, что в языке С++ гарантируется, что модификация переменной i под действием преинкремента произойдет до того, как будет завершено вычисление результата преинкремента. В языке С ничего подобного не гарантируется. Правила "если в одном выражении значение объекта меняется более одного раза" не существовало никогда и нигде. Более-менее правильной формой этого правила будет "если между парой соседних точек следования значение объекта модифицируется более одного раза, то поведение не определено". Также не следует забывать вторую часть этого правила: "если между парой соседних точек следования значение объекта модифицируется, а также присутствует независимое чтение значения этого объекта, то поведение не определено". После переработки в С++11 эти правила, однако, стали применимы только к языку С, но не к языку С++ (см. пример выше). Также стоит заметить, эти правила основаны на концепции точки следования, в то время как современные спецификации этих языков (как С++, так и С) решили отказаться от этой концепции и заменить ее концепцией упорядочения (sequencing, sequenced before, sequenced after). Но, еще раз, в языке С эти правила по-прежнему достаточно точно отражают ситуацию с UB в выражениях. Новое же правило, применимое как в С, так и в С++, звучит так Если побочный эффект, воздействующий на скалярный объект, неупорядочен (unsequenced) по отношению к другому побочному эффекту, воздействующему на этот же скалярный объект, или по отношению к вычислению значения этого же скалярного объекта, то поведение не определено. Различия же между С и С++ сводятся к отличающимся гарантиям того, что является упорядоченным (sequenced), а что нет (unsequenced). Упорядоченность оговаривается в описаниях конкретных операторов языка. В ваших примерах i = ++i + ++i; // UB и в С, и в С++ i = i++ + ++i; // UB и в С, и в С++ x = i++ + ++i; // UB и в С, и в С++ x = i++ + i++; // UB и в С, и в С++ a[i] += i++; // UB и в С, и в С++11, все в порядке в С++17 a[++i] = i++; // UB и в С, и в С++11, все в порядке в С++17 a[++i] = ++i // UB и в С, и в С++11, все в порядке в С++17 i += ++i; // UB в С, все в порядке в С++11 (?), все в порядке в С++17 j += j; // OK (Я не уверен в своей трактовке i += ++i. A += B определено через A = A + B, но i = i + ++i - это UB и в С++. Но подозреваю, что ситуацию спасает то, что A в A += B вычисляется только один раз.)

Комментариев нет:

Отправить комментарий