довольно таки часто обсуждаемая тема, но тем не меее хотелось бы конкретнее разобраться где есть 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 ?
Ответ
Языки С и С++ фундаментально отличаются в одной важной детали: язык С++ старается максимально тщательно сохранять 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 вычисляется только один раз.)
Комментариев нет:
Отправить комментарий