Ваш вопрос, вероятно, не был: «Почему эти конструкции неопределенного поведения в 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);
Возможно, кто-то дал вам этот код в виде головоломки. Этот код также не имеет смысла, особенно если вы запускаете его - и если вы скомпилируете и запустите его под двумя разными компиляторами, вы, вероятно, получите два разных ответа! Что с этим? Какой ответ правильный? (И ответ таков: оба они или нет).
Как вы уже слышали, все эти выражения undefined , что означает, что язык C не дает никаких гарантий относительно того, что они будут делать. Это странный и удивительный результат, потому что вы, вероятно, думали, что любая программа, которую вы могли бы написать, пока она компилируется и запускается, будет генерировать уникальный, четко определенный вывод. Но в случае неопределенного поведения это не так.
Что делает выражение неопределенным? Всегда ли выражения с ++
и --
не определены? Конечно, нет: это полезные операторы, и если вы используете их правильно, они совершенно четко определены.
Для выражений, о которых мы говорим, то, что делает их неопределенными, - это когда слишком много происходит одновременно, когда мы не уверены, в каком порядке произойдут события, но когда порядок имеет значение для результата, который мы получаем.
Давайте вернемся к двум примерам, которые я использовал в этом ответе. Когда я написал
printf("%d %d %d\n", x, ++x, x++);
вопрос перед вызовом printf
, компилятор сначала вычисляет значение x
, или 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
и модифицируем его.
Так вот и ответ: убедитесь, что в любом написанном вами выражении каждая переменная изменяется не более одного раза, и если переменная изменяется, вы также не пытаетесь использовать значение этой переменной где-либо еще.