Существует, по-видимому, довольно слабое понимание того, что означает «неопределенное поведение».
В C, C++ и родственных языках, таких как Objective-C, существует четыре вида поведения: существует поведение, определенное стандартом языка. Существует определенное поведение реализации, что означает, что в стандарте языка явно указано, что реализация должна определять поведение. Существует неопределенное поведение, когда в языковом стандарте указано, что возможны несколько вариантов поведения. И есть неопределенное поведение, , где языковой стандарт ничего не говорит о результате. Поскольку языковой стандарт ничего не говорит о результате, что-либо вообще может произойти с неопределенным поведением.
Некоторые люди предполагают, что «неопределенное поведение» означает «что-то плохое». Это неверно. Это означает, что «все может случиться», и это включает в себя «что-то плохое может случиться», а не «что-то плохое должно произойти». На практике это означает: «ничего плохого не происходит, когда вы проверяете свою программу, но как только она отправляется клиенту, все ад разрывается». Поскольку все может случиться, компилятор действительно может предположить, что в вашем коде нет неопределенного поведения - потому что либо оно истинно, либо оно ложно, и в этом случае что-то может случиться, а это значит, что все, что происходит из-за неправильного предположения компилятора, по-прежнему верный.
Кто-то утверждал, что когда p указывает на массив из трех элементов и вычисляется p + 4, ничего плохого не произойдет. Неправильно. Вот ваш оптимизационный компилятор. Скажи это ваш код:
int f (int x)
{
int a [3], b [4];
int* p = (x == 0 ? &a [0] : &b [0]);
p + 4;
return x == 0 ? 0 : 1000000/x;
}
Оценка р + 4 не определено поведение, если р указывает на [0], но если он указывает на б [0]. Поэтому компилятору разрешено считать, что p указывает на b [0]. Поэтому компилятору разрешено считать, что x! = 0, потому что x == 0 приводит к неопределенному поведению. Поэтому компилятору разрешено удалить проверку x == 0 в операторе return и просто вернуть 1000000/x. Это означает, что ваша программа вылетает, когда вы вызываете f (0) вместо возврата 0.
Другое предположение состояло в том, что если вы увеличиваете нулевой указатель и затем уменьшаете его снова, результат снова является нулевым указателем. Неправильно. Помимо возможности того, что приращение нулевого указателя может просто сработать на каком-то оборудовании, как насчет этого: поскольку приращение нулевого указателя не определено, компилятор проверяет, является ли указатель нулевым и только увеличивает указатель, если он не является нулевым указателем , поэтому p + 1 снова является нулевым указателем. И, как правило, он будет делать то же самое для декремента, но, будучи умным компилятором, замечает, что p + 1 всегда является неопределенным поведением, если результат был нулевым указателем, поэтому можно предположить, что p + 1 не является нулевым указателем, поэтому можно исключить проверку нулевого указателя. Это означает, что (p + 1) - 1 не является нулевым указателем, если p был нулевым указателем.
Мне любопытно, почему вы думаете, что это не будет ... – Borgleader
@ Борлингер из-за всех ограничений, о которых я говорил. Если бы это указывало на одно значение, оно было бы определено, но (только?), Потому что оно будет рассматриваться как указатель на массив размера 1. Что в этом случае? –
В конце концов, это всего лишь арифметика (указатель). Арифметика всегда четко определена. – Matt