Чи є a ^ a чи aa невизначеною поведінкою, якщо a не ініціалізовано?


76

Розглянемо цю програму:

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

На перший погляд, aце неініціалізована змінна. Це вказує на невизначену поведінку. Але a^aі a-aрівні 0для всіх значень a, принаймні я думаю, що це так. Чи можливо, що існує якийсь спосіб стверджувати, що поведінка чітко визначена?


Я би очікував, що це буде чітко визначено, оскільки значення a невідоме, але фіксоване, і воно не повинно змінюватися. Питання в тому, чи виділить компілятор місце для aподальшого читання із сміття, що там сидить. Якщо ні, то поведінка невизначена.
martin

Хм, доки змінна не позначена, volatileтоді я б прийняв це як визначену поведінку. a ^= a, точно еквівалентноa = 0
fileoffset

30
@martin: Це не виправлено. Значення дозволено змінювати. Це дуже практична думка. Змінній може бути присвоєний регістр ЦП, але, хоча він неініціалізований (тобто його дійсний час життя ще не розпочався), той самий регістр ЦП може бути зайнятий іншою змінною. Зміни в цій іншій змінній будуть розглядатися як "нестабільне" значення цієї неініціалізованої змінної. Це те, що часто спостерігається на практиці з неініціалізованими змінними.
АНТ

@AndreyT це гарне пояснення
мартін

1
Неважливо, знайшов, моя помилка: stackoverflow.com/questions/20300665/… , і це було насправді для К.
Томас

Відповіді:


73

У C11:

  • Це явно не визначено відповідно до 6.3.2.1/2, якщо aніколи не береться його адреса (цитується нижче)
  • Це може бути уявлення про пастку (що викликає UB при доступі). 6.2.6.1/5:

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

Непідписані ints можуть мати уявлення про пастки (наприклад, якщо він має 15 бітів точності та 1 біт парності, доступ aможе спричинити помилку парності).

6.2.4 / 6 говорить, що початкове значення є невизначеним, а визначення під п. 3.19.2 є або невизначеним значенням, або поданням пастки .

Далі: у C11 6.3.2.1/2, як зазначив Паскаль Куок:

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

Це не має винятку для типів символів, тому цей пункт, здається, замінює попереднє обговорення; доступ xнегайно не визначений, навіть якщо представлення пастки не існує. Цей пункт було додано до C11 для підтримки процесорів Itanium, які насправді мають стан захоплення для реєстрів.


Системи без уявлення про пастки: Але що, якщо ми кинемо &x;так, що заперечення 6.3.2.1/2 більше не застосовуватиметься, і ми знаходимося в системі, яка, як відомо, не має уявлень про пастки? Тоді значення є невизначеним значенням . Визначення невизначеного значення в 3.19.3 є дещо розмитим, проте воно пояснюється в DR 451 , який робить висновок:

  • Неініціалізоване значення за описаних умов може змінити його значення.
  • Будь-яка операція, виконана з невизначеними значеннями, матиме в результаті невизначене значення.
  • Функції бібліотеки виявлятимуть невизначену поведінку при використанні з невизначеними значеннями.
  • Ці відповіді підходять для всіх типів, які не мають уявлень про пастки.

Згідно з цією резолюцією, int a; &a; int b = a - a;результат bзалишається невизначеним.

Зверніть увагу, що якщо невизначене значення не передається функції бібліотеки, ми все ще знаходимося у сфері невизначеної поведінки (не невизначеної поведінки). Результати можуть бути дивними, наприклад, if ( j != j ) foo();можна подзвонити Фу, але демони повинні залишатися закріпленими в носовій порожнині.


Припускаючи, що ми знали, що не існує значень пастки, чи могли б ми аргументувати певну поведінку тоді?
Девід Хеффернан,

15
@DavidHeffernan Ви також можете поводитися з доступом до невизначених даних як з UB, оскільки ваш компілятор теж може, навіть якщо немає значень пастки. Будь ласка, див. Blog.frama-c.com/index.php?post/2013/03/13/…
Паскаль Куок,

@Pascal Я розумію це зараз. Це останній пункт відповіді Андрія.
Девід Хеффернан,

@DavidHeffernan Приклади йдуть настільки 2 * jдивні, що трохи гірше, ніж навіть картина у відповіді Андрія, але ти розумієш.
Паскаль Куок,

Коли було написано Стандарт C89, очікувалося, що реалізації визначатимуть багато речей, яких Стандарт не робив, і автори Стандарту не бачили підстав детально описувати всі випадки, коли дії слід розглядати як визначені щодо реалізацій, що визначають певні речі ( наприклад, той факт, що "unsigned int" не має уявлень про перехоплення), але невизначений для реалізацій, які цього не роблять (наприклад, коли читання невизначеного бітового шаблону як "unsigned int" може дати представлення trap).
supercat

32

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

По-перше, будь-яка неініціалізована змінна може мати "зламане" (воно ж "пастка") подання. Навіть одна спроба отримати доступ до цього подання викликає невизначену поведінку. Більше того, навіть об'єкти не захоплюючих типів (на зразок unsigned char) все ще можуть отримувати спеціальні стани, що залежать від платформи (наприклад, NaT - Not-A-Thing - на Itanium), що може виглядати як прояв їх "невизначеного значення".

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


1
Чи цитуєте ви цей останній абзац? Якщо це так, то нам навіть не потрібно розглядати пастки.
Девід Хеффернан,

2
@Matt McNabb: Ну, це може бути проблема, яка була вирішена по-різному через різні варіанти мовної специфікації. Але в резолюції DR # 260 ( open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm ) чітко чітко зазначено, що змінні з невизначеними значеннями можуть змінюватися довільно "самі по собі".
АНТ

4
@Matt McNabb: DR # 451 підтвердив фактично ті самі рішення DR # 260 як у жовтні 2013 р., Так і в квітні 2014 р . Open-std.org/Jtc1/sc22/WG14/www/docs/dr_451.htm . У відповіді комітету для DR # 451 прямо зазначено "Ця точка зору підтверджує позицію C99 DR260"
AnT

1
@hyde Найближче до уявлення про пастку, яке у вас може бути під рукою, - це сигналізація NaN. en.wikipedia.org/wiki/NaN#Signaling_NaN В іншому випадку вам потрібно отримати комп’ютер з явними бітами парності, комп’ютер із знаковою величиною, де –0 вважається значенням пастки, або щось настільки ж екзотичне.
Паскаль Куок,

1
@chux: Ні. Нічого не обмежує невизначену поведінку "робити те, що ти думаєш, а якщо ні, то затримує". Буквально будь-яка поведінка дозволена.
Ben Voigt

1

Якщо об’єкт має автоматичну тривалість зберігання і його адреса не береться, спроба його прочитати дасть невизначену поведінку. Беручи адресу такого об'єкта та використовуючи покажчики типу "unsigned char" для зчитування його байтів, Стандарт гарантує отримання значення типу "unsigned char", але не всі компілятори дотримуються цього стандарту. . ARM GCC 5.1, наприклад, коли дано:

згенерує код, який поверне x, якщо y дорівнює нулю, навіть якщо x знаходиться поза діапазоном 0-65535. Стандарт чітко пояснює, що беззнакові читання символів невизначеного значення гарантовано дають значення в діапазоні unsigned char, а поведінка параметра memmoveвизначається як еквівалентна послідовності читання та запису символів. Таким чином, temp2 повинен мати значення, яке могло б зберігатися в ньому через послідовність записів символів, але gcc вирішує замінити memmove присвоєнням та ігнорувати той факт, що код прийняв адресу temp1 і temp2.

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


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

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

@BeeOnRope: Їх обґрунтування полягає в тому, що невизначення поведінки не повинно заважати компіляторам визначати поведінку при націлюванні на процесори та поля додатків, де це було б практично та корисно зробити. На жаль, незалежно від того, чи повинні такі рішення Комітету мати такий ефект, очевидно, що вони мають.
supercat

Припускаю, так, вони могли ввести якийсь T std::freeze(T v)метод, який би перетворив "хитке" невизначене значення у невизначене, але стабільне значення. Це мало б корисність для "третього порядку": використання невизначеного значення вже малозрозуміле і використовується дуже рідко, тому додавання спеціальної конструкції для закріплення таких значень здавалося б, що йде все далі по кроличій норі того, що вже є незрозумілим кутом стандарт, і його потрібно було б підтримувати на основних етапах трансформації / оптимізації багатьох компіляторів.
BeeOnRope

@BeeOnRope: Можливість заморозити значення мала б по суті нульові витрати поза тими ситуаціями, коли це було б дуже важливо, і спроба налагодити оптимізований код за його відсутності - це вірний шлях до божевілля. Якщо хтось пише foo=moo; if (foo < 100) bar(foo);і mooнесподівано змінюється якимсь іншим потоком, спроба діагностувати, коли і де щось пішло не так, може бути по суті неможливою. Вміння вимовляти foo=moo; freeze(foo); if (foo < 100) bar(foo);і зобов’язувати компілятор до значення для fooзробило б речі набагато надійнішими.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.