Я читав про порядок порушень оцінювання , і вони дають приклад, який мене спантеличує.
1) Якщо побічний ефект на скалярний об'єкт не є послідовним щодо іншого побічного ефекту на той же скалярний об'єкт, поведінка не визначена.
// snip f(i = -1, i = -1); // undefined behavior
У цьому контексті i
є скалярний об’єкт , який, мабуть, означає
Арифметичні типи (3.9.1), типи перерахування, типи вказівників, вказівники на типи членів (3.9.2), std :: nullptr_t та версії цих типів, кваліфіковані cv (3.9.3), називаються у сукупності скалярними типами.
Я не бачу, наскільки ця заява є неоднозначною у тому випадку. Мені здається, що незалежно від того, чи оцінюється перший або другий аргумент спочатку, i
закінчується як -1
, і обидва аргументи також є -1
.
Може хтось, будь ласка, уточнить?
ОНОВЛЕННЯ
Я дуже ціную всю дискусію. Поки мені дуже подобається відповідь @ harmic, оскільки вона розкриває підводні камені та тонкощі визначення цього твердження, незважаючи на те, як прямо на перший погляд це виглядає. @ acheong87 вказує на деякі проблеми, які виникають при використанні посилань, але я думаю, що це ортогонально щодо аспекту побічних ефектів цього питання.
ПІДСУМОК
Оскільки це питання привернуло багато уваги, я підсуму основні моменти / відповіді. По-перше, дозвольте мені невеликий відступ, щоб зазначити, що "чому" може мати тісно пов'язані, але тонко різні значення, а саме "з якої причини ", "з якої причини " та "з якою метою ". Я згрупую відповіді, за яким із тих значень «чому» вони зверталися.
з якої причини
Основна відповідь тут приходить від Пола Дрейпера , причому Мартін Дж надав аналогічну, але не настільки велику відповідь. Відповідь Пола Дрейпера зводиться до
Це невизначена поведінка, оскільки не визначено, що таке поведінка.
Відповідь загалом дуже хороша з точки зору пояснення того, що говорить стандарт C ++. Він також розглядає деякі пов'язані випадки UB, такі як f(++i, ++i);
і f(i=1, i=-1);
. У першому із суміжних випадків незрозуміло, чи повинен бути перший аргумент, i+1
а другий i+2
чи навпаки; по-друге, не зрозуміло, чи i
повинно бути 1 або -1 після виклику функції. Обидва ці випадки є UB, оскільки вони підпадають під таке правило:
Якщо побічний ефект на скалярний об’єкт не є наслідком щодо іншого побічного ефекту на той же скалярний об’єкт, поведінка не визначена.
Тому f(i=-1, i=-1)
є також UB, оскільки він підпадає під одне і те ж правило, незважаючи на те, що наміри програміста (ІМХО) очевидні та однозначні.
Пол Дрейпер також чітко робить у своєму висновку, що
Чи могла бути визначена поведінка? Так. Це було визначено? Немає.
що підводить нас до питання "з якої причини / мети f(i=-1, i=-1)
залишилась невизначена поведінка?"
з якої причини / мети
Хоча в стандарті C ++ є деякі пропуски (можливо, необережні), багато упущень є обґрунтованими та служать певній меті. Хоча я усвідомлюю, що метою часто є «полегшити роботу автора-компілятора», або «швидший код», в основному мені було цікаво дізнатись, чи є вагома причина відпустки f(i=-1, i=-1)
як UB.
harmic і Supercat забезпечують основні відповіді , які забезпечують причину для UB. Хармік вказує, що оптимізуючий компілятор, який може розбити операції з нібито атомного присвоєння на декілька машинних інструкцій, і що він може додатково переплутати ці інструкції для досягнення оптимальної швидкості. Це може призвести до дуже дивних результатів: i
за його сценарієм дорівнює -2! Таким чином, шкідливість демонструє, як присвоєння одній і тій же величині змінній більше одного разу може мати негативні наслідки, якщо операції не є наслідком.
supercat надає пов'язану експозицію про підводні камені спроб змусити f(i=-1, i=-1)
зробити те, що схоже на те, що треба робити. Він вказує, що в деяких архітектурах існують жорсткі обмеження щодо декількох одночасних записів на одну і ту ж адресу пам'яті. Укладач міг би важко це зрозуміти, якби ми мали справу з чимось менш тривіальним, ніж f(i=-1, i=-1)
.
davidf також надає приклад інструкцій щодо переплетення, дуже схожих на шкідливі.
Хоча кожен із прикладів шкідливих, суперкотячих та давидфів дещо надуманий, узяте разом, вони все ще слугують чіткою причиною, чому f(i=-1, i=-1)
слід не визначати поведінку.
Я прийняв шкідливу відповідь, тому що це найкраще справлявся з усіма значеннями того, чому, навіть незважаючи на те, що відповідь Пола Дрейпера краще стосувалася частини "з якої причини".
інші відповіді
JohnB зазначає, що якщо ми розглянемо перевантажені оператори присвоєння (а не просто звичайні скаляри), то ми також можемо зіткнутися з проблемою.
f(i-1, i = -1)
чи щось подібне.
std::nullptr_t
та версії цих типів, кваліфіковані cv (3.9.3), називаються у сукупності скалярними типами . "