Не визначена поведінка та пункти послідовності


986

Що таке "точки послідовності"?

Яке відношення між невизначеною поведінкою та точками послідовності?

Я часто вживаю смішні та заплутані вирази на кшталт a[++i] = i;, щоб почувати себе краще. Чому я повинен припинити їх використання?

Якщо ви прочитали це, обов’язково перегляньте подальше запитання Невизначена поведінка та перезавантажені пункти послідовності .

(Примітка. Це означає, що це запис до C ++ FAQ Stack Overflow . Якщо ви хочете критикувати ідею надання поширених запитань у цій формі, то тут слід зробити публікацію про мета, яка почала все це . Відповіді на це питання відстежується в кімнаті для спілкування на C ++ , де ідея поширених запитань почалася в першу чергу, тому велику ймовірність отримати відповідь ті, хто придумав цю ідею.)

Відповіді:


682

C ++ 98 і C ++ 03

Ця відповідь стосується старих версій стандарту C ++. Версії стандарту C ++ 11 та C ++ 14 формально не містять "точок послідовності"; Операції замість цього «секвенуються раніше» або «непредменено» або «невизначено секвенуються». Чистий ефект по суті однаковий, але термінологія інша.


Відмова : Гаразд. Ця відповідь трохи довга. Тому запасіться терпінням, читаючи його. Якщо ви вже знаєте ці речі, їх читання знову не зведе з розуму.

Попередні реквізити : елементарне знання стандарту C ++


Що таке точки послідовності?

Стандарт говорить

У певних визначених пунктах послідовності виконання, що називаються пунктами послідовності , всі побічні ефекти попередніх оцінок повинні бути завершеними і жодних побічних ефектів наступних оцінок не має відбуватися. (§1.9 / 7)

Побічні ефекти? Що таке побічні ефекти?

Оцінка виразу створює щось, і якщо на додаток до цього відбувається зміна стану середовища виконання, говориться, що вираз (його оцінка) має певні побічні ефекти.

Наприклад:

int x = y++; //where y is also an int

Крім операції ініціалізації, значення yget змінюється завдяки побічному ефекту ++оператора.

Все йде нормально. Перехід до точок послідовності. Визначення чергування послідовних точок, поданих автором comp.lang.c Steve Summit:

Точка послідовності - це момент часу, коли пил осіла і всі побічні ефекти, які були помічені до цього часу, гарантовано завершені.


Які загальні точки послідовності перераховані у стандарті C ++?

Ті:

  • наприкінці оцінки повного вираження ( §1.9/16) (Повний вираз - це вираз, який не є субекспресією іншого виразу.) 1

    Приклад:

    int a = 5; // ; is a sequence point here
  • в оцінці кожного з наступних виразів після оцінки першого виразу ( §1.9/18) 2

    • a && b (§5.14)
    • a || b (§5.15)
    • a ? b : c (§5.16)
    • a , b (§5.18)(тут a, b - оператор з комами; в func(a,a++) ,- це не оператор з комами, це просто роздільник між аргументами aі a++. Таким чином, поведінка в цьому випадку не визначена (якщо aвважається примітивним типом))
  • при виклику функції (незалежно від того, функція вбудована чи ні), після оцінки всіх аргументів функції (якщо такі є), які мають місце до виконання будь-яких виразів або висловлювань у тілі функції ( §1.9/17).

1: Примітка: оцінка повновираження може включати оцінку піддепресій, які не лексично є частиною повного вираження. Наприклад, субекспресії, що беруть участь в оцінці виразів аргументів за замовчуванням (8.3.6), вважаються створеними в виразі, який викликає функцію, а не виразом, який визначає аргумент за замовчуванням.

2: Вказані оператори - це вбудовані оператори, як описано в пункті 5. Коли один з цих операторів перевантажений (п. 13) у вагомому контексті, таким чином позначаючи визначену користувачем функцію оператора, вираз позначає виклик функції та операнди формують список аргументів, не маючи на увазі точку послідовності між ними.


Що таке не визначена поведінка?

Стандарт визначає не визначене поведінку в розділі §1.3.12як

поведінка, яка може виникнути при використанні помилкової конструкції програми або помилкових даних, до яких цей Міжнародний стандарт не пред'являє жодних вимог 3 .

Не визначена поведінка може також очікуватися, коли цей Міжнародний стандарт опускає опис будь-якого явного визначення поведінки.

3: допустима невизначена поведінка варіюється від ігнорування ситуації повністю з непередбачуваними результатами, до поведінки під час перекладу чи виконання програми в документально підтвердженому для навколишнього середовища способі (з або без видачі діагностичного повідомлення), до припинення перекладу чи виконання (з видачею діагностичного повідомлення).

Коротше кажучи, невизначена поведінка означає, що все може статися від демонів, що вилітають з носа, до того, як ваша дівчина завагітніла.


Яке відношення між невизначеною поведінкою та точками послідовності?

Перш ніж я перейду до цього, ви повинні знати різницю між невизначеною поведінкою, невизначеною поведінкою та визначеною поведінкою поведінкою .

Ви також повинні це знати the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.

Наприклад:

int x = 5, y = 6;

int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.

Ще один приклад тут .


Тепер Стандарт §5/4говорить

  • 1) Між попередньою та наступною точкою послідовності скалярний об'єкт повинен змінити його збережене значення якнайбільше одночасно оцінкою виразу.

Що це означає?

Неофіційно це означає, що між двома точками послідовності змінна не повинна бути змінена більше одного разу. У виразі виразів, next sequence pointяк правило, previous sequence pointзнаходиться в кінці крапки з комою, а значення знаходиться в кінці попереднього твердження. Вираз також може містити проміжний продукт sequence points.

З наведеного речення наступні вирази викликають не визначене поведінку:

i++ * ++i;   // UB, i is modified more than once btw two SPs
i = ++i;     // UB, same as above
++i = 2;     // UB, same as above
i = ++i + 1; // UB, same as above
++++++i;     // UB, parsed as (++(++(++i)))

i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)

Але такі вирази чудові:

i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i);   // well defined 
int j = i;
j = (++i, i++, j*i); // well defined

  • 2) Крім того, доступ до попереднього значення має лише для визначення значення, яке потрібно зберігати.

Що це означає? Це означає, що якщо об'єкт записаний в повному виразі, будь-який і всі звернення до нього в межах одного виразу повинні бути безпосередньо залучені до обчислення значення, яке потрібно записати .

Наприклад, у i = i + 1всьому доступі i(в LHS та RHS) безпосередньо беруть участь у обчисленні записаного значення. Так це добре.

Це правило фактично обмежує юридичні вирази до тих, у яких доступ, очевидно, передує зміні.

Приклад 1:

std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2

Приклад 2:

a[i] = i++ // or a[++i] = i or a[i++] = ++i etc

заборонено, тому що один із доступів i(той, що знаходиться в a[i]) не має нічого спільного зі значенням, яке в кінцевому підсумку зберігається в i (що відбувається над в i++), і тому немає жодного хорошого способу визначення - ні для нашого розуміння, ні для компілятор - чи має відбуватися доступ до або після збереження нарощеного значення. Тож поведінка не визначена.

Приклад 3:

int x = i + i++ ;// Similar to above

Слідуюча відповідь на C ++ 11 тут .


45
*p++ = 4 не визначена поведінка. *p++трактується як *(p++). p++повертає p(копію) та значення, збережене за попередньою адресою. Навіщо це викликати UB? Це абсолютно добре.
Prasoon Saurav

6
@Mike: AFAIK, немає (законних) копій стандарту C ++, на який ви могли б посилатися.
sbi

11
Ну, тоді ви можете мати посилання на відповідну сторінку замовлення ISO. У будь-якому випадку, думаючи про це, фраза "елементарне знання стандарту C ++" здається трохи суперечливою в плані, оскільки якщо ти читаєш стандарт, ти проходить елементарний рівень. Може, ми могли б перерахувати, які речі в мові вам потрібні для базового розуміння, як синтаксис виразів, порядок операцій та, можливо, перевантаження оператора?
Майк Десімоне

41
Я не впевнений, що цитування стандарту є найкращим способом навчання новачків
Зворотний

6
@Adrian Перший вираз викликає UB, оскільки між останньою ++iта присвоєнням до не існує точки послідовності i. Другий вираз не викликає UB, оскільки вираз iне змінює значення i. У другому прикладі i++після ,виклику оператора призначення призначається точка послідовності ( ).
Колюня

276

Це подання до моєї попередньої відповіді і містить матеріали, пов'язані зі С ++ 11. .


Попередні реквізити : елементарне знання з відносин (математика).


Чи правда, що в C ++ 11 немає точок послідовності?

Так! Це дуже правда.

Послідовність Окуляри були замінені Sequenced Перед і Sequenced ПісляUnsequenced і невизначено Sequenced ) відносин в C ++ 11.


Що саме це за «Послідовність раніше»?

Послідовність раніше (§1.9 / 13) - це відношення, яке є:

між оцінками, виконаними однією ниткою, і викликає суворий частковий порядок 1

Формально це означає, що дають будь-які дві оцінки (Див. Нижче), A і B, якщо Aвони прописані раніше B , то виконанню A передує виконання B. Якщо AНЕ секвенували до того Bі BНЕ секвенували до того A, то Aі Bє unsequenced 2 .

Оцінки Aі Bє невизначено секвенували , коли або Aсеквенували перед тим Bабо Bсеквеніруют перед тим A, але це не визначено , яке 3 .

[Примітки]
1: Строгий частковий порядок є бінарне відношення "<" на безлічі , Pяке є asymmetric, і transitive, тобто для всіх a, bі cв P, маємо:
........ (я). якщо a <b, то ¬ (b <a) ( asymmetry);
........ (ii). якщо a <b і b <c, то a <c ( transitivity).
2: Виконання непослідовних оцінок може перекриватися .
3: Неозначено послідовні оцінки не можуть перетинатись , але вони можуть бути виконані спочатку.


Яке значення слова "оцінка" в контексті C ++ 11?

У C ++ 11 оцінка виразу (або підвиразу) загалом включає:

  • обчислення значень (включаючи визначення ідентичності об'єкта для оцінювання glvalue та отримання значення, попередньо призначеного об'єкту для оцінки першого значення ) та

  • ініціювання побічних ефектів .

Тепер (§1.9 / 14) сказано:

Кожне обчислення значення та побічний ефект, пов'язаний з повною експресією, секвенується перед кожним обчисленням значення та побічним ефектом, пов'язаним з наступним оцінюванням повного вираження .

  • Тривіальний приклад:

    int x; x = 10; ++x;

    Обчислення ++xвеличини та пов'язаних з ними побічних ефектів секвенують після обчислення значення та побічного ефектуx = 10;


Тож має бути певний зв’язок між невизначеною поведінкою та вищезгаданими речами, правда?

Так! Правильно.

У (§1.9 / 15) було зазначено, що

За винятком випадків, де зазначено, операнди окремих операторів та субекспресії окремих виразів не є наслідком 4 .

Наприклад :

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. Оцінка операндів +оператора не є послідовною відносно один одного.
  2. Оцінка операндів операторів <<і >>операторів не є наслідком відносно один одного.

4: У виразі, який оцінюється не один раз під час виконання програми, неоперативні та невизначено послідовно оцінені його підвиразки не повинні виконуватися послідовно в різних оцінках.

(§1.9 / 15) Обчислення значень операндів оператора секвенуються перед обчисленням значення результату оператора.

Це означає, що в x + yобчисленні значень xі yпрослідковуються послідовності перед обчисленням значення (x + y).

Що ще більш важливо

(§1.9 / 15) Якщо побічний ефект на скалярний об’єкт не є наслідком щодо будь-якого

(а) інший побічний ефект на той же скалярний об'єкт

або

(b) обчислення значення, використовуючи значення того ж скалярного об'єкта.

поведінка не визначена .

Приклади:

int i = 5, v[10] = { };
void  f(int,  int);
  1. i = i++ * ++i; // Undefined Behaviour
  2. i = ++i + i++; // Undefined Behaviour
  3. i = ++i + ++i; // Undefined Behaviour
  4. i = v[i++]; // Undefined Behaviour
  5. i = v[++i]: // Well-defined Behavior
  6. i = i++ + 1; // Undefined Behaviour
  7. i = ++i + 1; // Well-defined Behaviour
  8. ++++i; // Well-defined Behaviour
  9. f(i = -1, i = -1); // Undefined Behaviour (see below)

Під час виклику функції (незалежно від того, чи є функція вбудованою чи ні), кожне обчислення значення та побічний ефект, пов'язаний з будь-яким виразом аргументу, або з виразом постфікса, що позначає викликану функцію, секвенуються перед виконанням кожного виразу або висловлювання в тілі називається функцією. [ Примітка: Обчислення значень та побічні ефекти, пов'язані з різними виразами аргументів, не мають наслідків . - кінцева примітка ]

Вирази (5), (7)і (8)не викликають невизначений поведінка. Перегляньте наступні відповіді для більш детального пояснення.


Заключна примітка :

Якщо ви знайдете якісь недоліки у публікації, залиште коментар. Користувачі, які користуються владою (З реп.> 20000), будь ласка, не соромтесь редагувати публікацію для виправлення помилок друку та інших помилок.


3
Замість "асиметричних" послідовність до / після - "антисиметричні" відносини. Це слід змінити в тексті, щоб відповідати визначенню пізніше часткового наказу (що також погоджується з Вікіпедією).
TemplateRex

1
Чому 7) елемент в останньому прикладі є UB? Може, так і має бути f(i = -1, i = 1)?
Михайло

1
Я зафіксував опис відношення «послідовно раніше». Це суворий частковий порядок . Очевидно, вираз не може бути послідовним перед собою, тому відношення не може бути рефлексивним. Отже, вона несиметрична, а не симетрична.
ThomasMcLeod

1
5) добрий вигляд підірвав мій розум. пояснення Йоханнеса Шауба було не зовсім зрозумілим. Тим більше, що я вважав, що навіть у ++i(оцінюється значення перед +оператором, який його використовує), стандарт все ще не говорить про те, що його побічний ефект повинен бути закінчений. Але насправді, оскільки він повертає коефіцієнт, lvalueякий відповідає iсамому собі, він ОБОВ'ЯЗКОВО повинен закінчити побічний ефект, оскільки оцінка має бути закінчена, тому значення має бути оновленим. Це було божевільною частиною, щоб отримати насправді.
v.oddou

"Члени Комітету ISO C ++ вважали, що пункти Sequence Points досить важкі для розуміння. Тому вони вирішили замінити це вищезгаданими відносинами лише для більш чіткого формулювання та підвищення точності". - у вас є довідка на цю претензію? Мені здається, що нові стосунки важче зрозуміти.
М.М.

30

C ++ 17 ( N4659) включає пропозицію Уточнення порядку вираження виразів для ідіоматичного C ++, яка визначає більш суворий порядок оцінки вираження.

Зокрема, наступне речення

8.18 Оператори призначення та складання операторів призначення :
....

У всіх випадках призначення присвоюється послідовно після обчислення значення правого і лівого операндів і перед обчисленням значення виразу призначення. Правий операнд секвенується перед лівим операндом.

разом із наступним уточненням

Вираз Х називається секвенувати перед виразом Y , якщо кожне обчислення значення , і кожен побічний ефект , пов'язаний з виразом X секвеніруют перед кожним обчислення значень і кожного побічного ефекту , пов'язаного з виразом Y .

зробити кілька випадків раніше визначеної поведінки дійсними, включаючи розглянутий:

a[++i] = i;

Однак кілька інших подібних випадків все ще призводять до невизначеної поведінки.

В N4140:

i = i++ + 1; // the behavior is undefined

Але в N4659

i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined

Звичайно, використання компілятора, сумісного з C ++ 17, не означає, що слід починати писати такі вирази.


чому i = i++ + 1;визначено поведінку в c ++ 17, я думаю, навіть якщо "Правий операнд секвенується перед лівим операндом", проте модифікація для "i ++" та побічний ефект для призначення не є наслідком, будь ласка, надайте більше деталей для їх інтерпретації
jack X

@jackX я продовжив відповідь :).
AlexD

так, я вважаю, що детальніше інтерпретації речення "Правий операнд секвенується перед лівим операндом" є більш корисним. Так, як "Правий операнд секвенується перед лівим операндом" означає, що обчислення значень і побічний ефект, пов'язаний з правим операндом, є секвенсований до цього лівого операнда. як ви це зробили :-)
jack X

11

Я здогадуюсь, що існує фундаментальна причина змін, і не просто косметично зробити старі інтерпретації зрозумілішими: ця причина - це одночасність. Невизначений порядок опрацювання - це лише вибір однієї з кількох можливих послідовних замовлень, це зовсім інакше, ніж до і після замовлення, тому що якщо не визначене замовлення, можлива паралельна оцінка: не так, як зі старими правилами. Наприклад у:

f (a,b)

попередньо або a, потім b, або, b потім a. Тепер, a і b можна оцінити за допомогою перемежованих інструкцій або навіть на різних ядрах.


5
Я вважаю, що якщо або "a", або "b" включає виклик функції, вони невизначено секвенуються, а не без наслідків, що означає, що всі побічні ефекти від одного повинні виникати перед будь-якими побічними ефектами з боку інше, хоча компіляторові не потрібно узгоджувати, який із них виходить першим. Якщо це більше не відповідає дійсності, він би порушив багато коду, який спирається на операції, що не перетинаються (наприклад, якщо "a" і "b" кожен встановлює, використовує і знімає загальний статичний стан).
supercat

2

Що, C99(ISO/IEC 9899:TC3)здається, відсутнє у цій дискусії, поки що зроблено наступні стентенти щодо порядку оцінювання.

[...] порядок оцінки підекспресій та порядок здійснення побічних ефектів не визначені. (Розділ 6.5 с. 67)

Порядок оцінки операндів не визначений. Якщо робиться спроба змінити результат оператора присвоєння або отримати доступ до нього після наступної точки послідовності, поведінка [sic] не визначена. (Розділ 6.5.16, стор. 91)


2
Питання позначено як C ++, а не C, що добре, тому що поведінка в C ++ 17 сильно відрізняється від поведінки у старих версіях - і не має ніякого відношення до поведінки у C11, C99, C90 тощо. Або несе дуже мало відношення до нього. В цілому, я б запропонував це зняти. Більш суттєво, що нам потрібно знайти еквівалент Q & A для C і переконатися, що це нормально (і зазначає, що C ++ 17, зокрема, змінює правила - поведінка в C ++ 11 і раніше була більш-менш такою ж, як в C11, хоча у багатослівному описі його на C все ще використовується "точка послідовності", тоді як C ++ 11 і пізніше немає
Джонатан Леффлер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.