C ++ 98 і C ++ 03
Ця відповідь стосується старих версій стандарту C ++. Версії стандарту C ++ 11 та C ++ 14 формально не містять "точок послідовності"; Операції замість цього «секвенуються раніше» або «непредменено» або «невизначено секвенуються». Чистий ефект по суті однаковий, але термінологія інша.
Відмова : Гаразд. Ця відповідь трохи довга. Тому запасіться терпінням, читаючи його. Якщо ви вже знаєте ці речі, їх читання знову не зведе з розуму.
Попередні реквізити : елементарне знання стандарту C ++
Що таке точки послідовності?
Стандарт говорить
У певних визначених пунктах послідовності виконання, що називаються пунктами послідовності , всі побічні ефекти попередніх оцінок повинні бути завершеними і жодних побічних ефектів наступних оцінок не має відбуватися. (§1.9 / 7)
Побічні ефекти? Що таке побічні ефекти?
Оцінка виразу створює щось, і якщо на додаток до цього відбувається зміна стану середовища виконання, говориться, що вираз (його оцінка) має певні побічні ефекти.
Наприклад:
int x = y++; //where y is also an int
Крім операції ініціалізації, значення y
get змінюється завдяки побічному ефекту ++
оператора.
Все йде нормально. Перехід до точок послідовності. Визначення чергування послідовних точок, поданих автором 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 тут .
*p++ = 4
не визначена поведінка.*p++
трактується як*(p++)
.p++
повертаєp
(копію) та значення, збережене за попередньою адресою. Навіщо це викликати UB? Це абсолютно добре.