Ваш вопрос, вероятно, не был: «Почему эти конструкции неопределенного поведения в C?». Ваш вопрос был, вероятно, «Почему этот код (использование ++
) не дал мне ожидаемого значения?», И кто-то отметил ваш вопрос как дубликат и отправил вас сюда.
Этот ответ пытается ответить на этот вопрос: почему ваш код не дал ожидаемого вами ответа, и как вы можете научиться распознавать (и избегать) выражения, которые не будут работать должным образом.
Я предполагаю, что вы уже слышали базовое определение C ++
и --
операторов, и чем форма префикса ++x
отличается от формы постфикса x++
. Но об этих операторах трудно думать, поэтому, чтобы убедиться, что вы поняли, возможно, вы написали крошечную тестовую программу, включающую что-то вроде
int x = 5;
printf("%d %d %d\n", x, ++x, x++);
Но, к вашему удивлению, эта программа не помогла вам понять - она напечатала какой-то странный, неожиданный, необъяснимый вывод, предполагая, что, возможно, ++
делает что-то совершенно другое, совсем не то, что вы думали.
Или, возможно, вы смотрите на трудное для понимания выражение, как
int x = 5;
x = x++ + ++x;
printf("%d\n", x);
Возможно, кто-то дал вам этот код в виде головоломки. Этот код также не имеет смысла, особенно если вы запускаете его - и если вы скомпилируете и запустите его под двумя разными компиляторами, вы, вероятно, получите два разных ответа! Что с этим? Какой ответ правильный? (И ответ таков: оба они или нет)
Как вы уже слышали, все эти выражения не определены , что означает, что язык C не дает никаких гарантий относительно того, что они будут делать. Это странный и удивительный результат, потому что вы, вероятно, думали, что любая программа, которую вы могли бы написать, пока она компилируется и запускается, будет генерировать уникальный, четко определенный вывод. Но в случае неопределенного поведения это не так.
Что делает выражение неопределенным? Являются ли выражения вовлеченными ++
и --
всегда неопределенными? Конечно, нет: это полезные операторы, и если вы используете их правильно, они совершенно четко определены.
Для выражений, о которых мы говорим, то, что делает их неопределенными, - это когда слишком много происходит одновременно, когда мы не уверены, в каком порядке произойдут события, но когда порядок имеет значение для результата, который мы получаем.
Давайте вернемся к двум примерам, которые я использовал в этом ответе. Когда я написал
printf("%d %d %d\n", x, ++x, x++);
вопрос перед вызовом printf
, компилятор вычисляет значение x
first, или x++
, или может быть ++x
? Но оказывается, что мы не знаем . В C нет правила, согласно которому аргументы функции оцениваются слева направо, справа налево или в каком-либо другом порядке. Поэтому мы не можем сказать , будет ли компилятор сделать x
первый, затем ++x
, затем x++
, или x++
потом ++x
потом x
, или какой -либо другой порядок. Но порядок явно имеет значение, потому что в зависимости от того, какой порядок использует компилятор, мы получим разные результаты printf
.
Как насчет этого сумасшедшего выражения?
x = x++ + ++x;
Проблема с этим выражением состоит в том, что оно содержит три различные попытки изменить значение x: (1) x++
деталь пытается добавить 1 к x, сохранить новое значение x
и вернуть старое значение x
; (2) ++x
деталь пытается добавить 1 к x, сохранить новое значение x
и вернуть новое значение x
; и (3) x =
часть пытается присвоить сумму двух других обратно x. Какое из этих трех попыток будет «выиграно»? Какое из трех значений будет назначено x
? Опять же, и, возможно, что удивительно, в Си нет правил, чтобы говорить нам.
Вы можете себе представить, что приоритет или ассоциативность или оценка слева направо говорит вам, в каком порядке происходят вещи, но это не так. Вы можете мне не верить, но, пожалуйста, поверьте мне на слово, и я скажу это снова: приоритет и ассоциативность не определяют каждый аспект порядка вычисления выражения в C. В частности, если в одном выражении есть несколько различные места, где мы пытаемся присвоить новое значение чему-то вроде x
приоритета и ассоциативности, не говорят нам, какая из этих попыток происходит первой, последней или какой-либо другой.
Итак, со всем этим фоном и введением, если вы хотите убедиться, что все ваши программы четко определены, какие выражения вы можете написать, а какие вы не можете написать?
Эти выражения все в порядке:
y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;
Все эти выражения не определены:
x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);
И последний вопрос: как определить, какие выражения определены, а какие нет?
Как я сказал ранее, неопределенные выражения - это те, в которых слишком много всего происходит одновременно, где вы не можете быть уверены, в каком порядке происходят вещи и где порядок имеет значение:
- Если есть одна переменная, которая модифицируется (присваивается) в двух или более разных местах, как узнать, какая модификация произойдет первой?
- Если есть переменная, которая модифицируется в одном месте, а ее значение используется в другом месте, как вы узнаете, использует ли оно старое или новое значение?
Как пример № 1, в выражении
x = x++ + ++x;
Есть три попытки изменить `x.
Как пример № 2, в выражении
y = x + x++;
мы оба используем значение x
и изменяем его.
Вот и ответ: убедитесь, что в любом написанном вами выражении каждая переменная изменяется не более одного раза, и если переменная изменяется, вы также не пытаетесь использовать значение этой переменной где-либо еще.