Немає розумного, що компілятор міг би зробити, щоб отримати результат 6, але це можливо і правомірно. Результат 4 є цілком обґрунтованим, і я б вважав результат 5 прикордонним розумним. Всі вони є абсолютно законними.
Гей, почекай! Хіба не ясно, що має статися? Додавання потребує результатів двох приростів, тому, очевидно, вони повинні відбутися спочатку. І ми йдемо зліва направо, так що ... Ох! Якби тільки це було так просто. На жаль, це не так. Ми не йдемо зліва направо, і в цьому проблема.
Зчитування розташування пам'яті у двох регістрах (або ініціалізація їх обох з одного літералу, оптимізація зворотного шляху до пам'яті) є дуже розумною справою для компілятора. Це фактично призведе до того, що потайно існують дві різні змінні, кожна зі значенням 2, яка, нарешті, буде додана до результату 4. Це "розумно", оскільки це швидко і ефективно, і це відповідає обом стандарт і з кодом.
Подібним чином, розташування пам'яті можна прочитати один раз (або змінну, ініціалізовану з літералу) і збільшити один раз, а тіньову копію в іншому регістрі можна збільшити після цього, що призведе до додавання 2 і 3. Це, я б сказав, граничне обґрунтування, хоча і цілком законне. Я вважаю це прикордонним розумним, оскільки це не те чи інше. Це не "розумний" оптимізований спосіб, і не "розумний" точно педантичний спосіб. Це дещо посередині.
Двічі збільшити місце пам’яті (що призводить до значення 3), а потім додати це значення до себе для кінцевого результату 6 є правомірним, але не зовсім розумним, оскільки робити об’їми пам’яті не є точно ефективними. Хоча на процесорі з гарною переадресацією сховища, це цілком може бути "розумно" зробити, оскільки магазин повинен бути в основному невидимим ...
Оскільки компілятор "знає", що це одне і те ж місце, він також може вибрати збільшення значення двічі в реєстрі, а потім також додайте його до себе. Будь-який із підходів дасть вам результат 6.
За формулюванням стандарту, компілятору дозволено давати вам будь-який такий результат, хоча я особисто вважав би 6 майже пам’яткою "ебать вас" від Неприємного департаменту, оскільки це досить несподівана річ (юридична чи ні, намагатися завжди робити найменшу кількість сюрпризів - це добре зробити!). Хоча, бачачи, як пов'язана невизначена поведінка, на жаль, не можна справді сперечатися про "несподіване", так.
Отже, насправді, який код у вас є для компілятора? Давайте запитаємо clang, який покаже нам, якщо ми красиво запитаємо (викликаючи за допомогою -ast-dump -fsyntax-only
):
ast.cpp:4:9: warning: multiple unsequenced modifications to 'i' [-Wunsequenced]
int x = ++i + ++i;
^ ~~
(some lines omitted)
`-CompoundStmt 0x2b3e628 <line:2:1, line:5:1>
|-DeclStmt 0x2b3e4b8 <line:3:1, col:10>
| `-VarDecl 0x2b3e430 <col:1, col:9> col:5 used i 'int' cinit
| `-IntegerLiteral 0x2b3e498 <col:9> 'int' 1
`-DeclStmt 0x2b3e610 <line:4:1, col:18>
`-VarDecl 0x2b3e4e8 <col:1, col:17> col:5 x 'int' cinit
`-BinaryOperator 0x2b3e5f0 <col:9, col:17> 'int' '+'
|-ImplicitCastExpr 0x2b3e5c0 <col:9, col:11> 'int' <LValueToRValue>
| `-UnaryOperator 0x2b3e570 <col:9, col:11> 'int' lvalue prefix '++'
| `-DeclRefExpr 0x2b3e550 <col:11> 'int' lvalue Var 0x2b3e430 'i' 'int'
`-ImplicitCastExpr 0x2b3e5d8 <col:15, col:17> 'int' <LValueToRValue>
`-UnaryOperator 0x2b3e5a8 <col:15, col:17> 'int' lvalue prefix '++'
`-DeclRefExpr 0x2b3e588 <col:17> 'int' lvalue Var 0x2b3e430 'i' 'int'
Як бачите, той самий lvalue Var 0x2b3e430
префікс ++
застосовано у двох місцях, і ці два знаходяться під одним вузлом у дереві, що є дуже неспеціальним оператором (+), про який не сказано нічого особливого щодо послідовності чи подібного. Чому це важливо? Ну, читайте далі.
Зверніть увагу на попередження: "безліч послідовних модифікацій" i "" . О, о, це не звучить добре. Що це означає? [basic.exec] розповідає нам про побічні ефекти та послідовності та повідомляє (параграф 10), що за замовчуванням, якщо прямо не сказано інше, оцінки операндів окремих операторів та підвиразів окремих виразів не є наслідками . Ну, боже, така справа operator+
- нічого не сказано інакше, тому ...
Але чи ми дбаємо про секвенування раніше, невизначено-секвенування чи несеквенцію? Хто б хотів знати?
Цей самий параграф також повідомляє нам, що оцінки без послідовностей можуть перекриватися, і що коли вони посилаються на одне і те ж місце пам'яті (це так!), І що це не є потенційно одночасним, тоді поведінка не визначена. Тут справді стає потворно, бо це означає, що ви нічого не знаєте і не маєте жодних гарантій щодо того, щоб бути «розумним». Нерозумна річ насправді цілком допустима і "розумна".