Напевно, ваше запитання не було: "Чому ці конструкції не визначені в 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);
Можливо, хтось подарував вам цей код як головоломку. Цей код також не має сенсу, особливо якщо ви його запускаєте - і якщо ви компілюєте та запускаєте його під двома різними компіляторами, ви, ймовірно, отримаєте дві різні відповіді! Що з цим? Яка відповідь правильна? (І відповідь полягає в тому, що вони обоє є, або жодна з них не є.)
Як ви вже чули, усі ці вирази не визначені , а це означає, що мова С не дає жодних гарантій того, що вони будуть робити. Це дивний і дивовижний результат, тому що ви, напевно, думали, що будь-яка програма, яку ви можете написати, доки вона складе та запустить, створить унікальний, чітко визначений вихід. Але у випадку невизначеної поведінки це не так.
Що робить вираз невизначеним? Чи є виразами, що стосуються ++
та--
завжди не визначені? Звичайно, ні: це корисні оператори, і якщо ви правильно їх використовуєте, вони чудово визначені.
Для виразів ми говоримо про те, що робить їх невизначеними - це коли надто багато відбувається відразу, коли ми не впевнені, в якому порядку відбудуться речі, але коли порядок має значення для результату, який ми отримуємо.
Повернемось до двох прикладів, які я використав у цій відповіді. Коли я писав
printf("%d %d %d\n", x, ++x, x++);
перед тим, як викликати printf
, компілятор обчислює значення x
першого, або x++
, чи, може ++x
,? Але виявляється, ми не знаємо . Не існує жодного правила, яке говорить про те, що аргументи функції оцінюються зліва направо, або справа наліво, або в іншому порядку. Тому ми не можемо сказати , чи буде компілятор зробити 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
? Знову, і, мабуть, дивно, в C немає правила сказати нам.
Ви можете собі уявити, що пріоритетність чи асоціативність чи оцінка зліва направо говорить вам, в якому порядку відбуваються речі, але вони не роблять. Ви можете мені не повірити, але будь ласка, прийміть моє слово, і я повторю це: пріоритетність та асоціативність не визначають кожен аспект порядку оцінювання виразу у 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
та змінюємо його.
Отож, це відповідь: переконайтеся, що в будь-якому написаному виразі кожна змінна змінюється не більше одного разу, і якщо змінна змінена, ви також не намагаєтесь використовувати значення цієї змінної деінде.