(Чому) використовує невизначену змінну невизначену поведінку?


82

Якщо я маю:

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

Два питання:

  • Чи дійсно поведінка цього коду невизначена?
    (Наприклад, може збій коду [чи гірше] у сумісній системі?)

  • Якщо так, чому С каже, що поведінка невизначена, коли абсолютно ясно, що тут xмає бути нуль?

    тобто яка перевага дається, якщо не визначати поведінку тут?

Очевидно, що компілятор міг просто використати все значення сміття, яке він вважав "зручним", всередині змінної, і це буде працювати за призначенням ... що поганого в такому підході?



3
Яку перевагу дає визначення особливого випадку для поведінки тут? Звичайно, всі ми можемо робити наші програми та бібліотеки більшими та повільнішими, оскільки @Mehrdad хоче уникнути ініціалізації змінної в одному конкретному і рідкісному випадку.
Пол Томблін,

9
@ W'rkncacnter Я не погоджуюсь з тим, що це дурень. Незалежно від того, яке значення воно приймає, ОП очікує, що після цього воно буде нульовим x -= x. Питання в тому, чому доступ до неініціалізованих цінностей взагалі є UB.
Містичний

6
Цікаво, що твердження x = 0; зазвичай перетворюється на xor x, x у збірці. Це майже те саме, що ви намагаєтесь зробити тут, але з xor замість віднімання.
0xFE

1
'тобто яка перевага дається тим, що тут не визначається поведінка? '- Я б міг подумати, що перевага стандарту в тому, що не перераховується нескінченність виразів зі значеннями, які не залежать від однієї або декількох змінних, є очевидною. Водночас, @Paul, така зміна стандарту не зробить програми та бібліотеки більшими.
Jim Balter

Відповіді:


90

Так, така поведінка невизначена, але з інших причин, ніж відомо більшості людей.

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

Що робить поведінку невизначеною, це додаткова властивість вашої змінної, а саме те, що вона "могла бути оголошена за допомогою register", тобто її адреса ніколи не береться. Такі змінні обробляються спеціально, оскільки існують архітектури, які мають реальні регістри ЦП, які мають якийсь додатковий стан, який «неініціалізований» і не відповідає значенню в домені типу.

Редагувати: Відповідною фразою стандарту є 6.3.2.1p2:

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

А щоб було зрозуміліше, такий кодекс є законним за будь-яких обставин:

  • Тут адреси aта bберуться, тому їх значення просто невизначене.
  • Оскільки unsigned charніколи не існує уявлень про пастки, що невизначене значення є просто невстановленим, unsigned charможе статися будь-яке значення .
  • В кінці a повинен містити значення 0.

Edit2: a і bмають невизначені значення:

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


6
Можливо, я чогось пропускаю, але мені здається, що вони unsignedможуть мати уявлення про пастки. Чи можете ви вказати на частину стандарту, яка так говорить? Я бачу в §6.2.6.2 / 1 таке: "Для цілочисельних типів без підпису, крім беззнакових символів , біти представлення об'єкта повинні бути розділені на дві групи: біти значення та біти доповнення (останні не повинні бути жодними). ... це повинно бути відоме як представлення значень. Значення будь-яких бітів заповнення не вказані. ⁴⁴⁾ "із коментарем:" ⁴⁴⁾ Деякі комбінації бітів заповнення можуть генерувати уявлення про пастку ".
Коніо

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

4
@conio Ви маєте рацію щодо всіх типів, крім unsigned char, але ця відповідь використовується unsigned char. Зверніть увагу, однак: суворо відповідна програма може розрахувати sizeof(unsigned) * CHAR_BITта визначити, виходячи з того UINT_MAX, що певні реалізації не можуть мати уявлення про пастки unsigned. Після того, як ця програма визначилася, вона може продовжувати робити саме те, з чим відповідає ця відповідь unsigned char.

4
@JensGustedt: Хіба це не memcpyвідволікає увагу, тобто чи не застосовуватиметься ваш приклад, якби його замінили на *&a = *&b;.
R .. GitHub СТОП ДОПОМОГАЙ ЛЕД

4
@R .. Я вже не впевнений. Триває обговорення списку розсилки Комітету С, і, здається, все це є великим хаосом, а саме великим розривом між тим, що передбачається (або було передбачено) поведінкою, і тим, що насправді записано. Однак зрозуміло те, що доступ до пам’яті як unsigned charі, таким чином, memcpyдопомагає, той, що *&є менш зрозумілим. Повідомлю, як тільки це врегулюється.
Йенс Густедт

24

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

Примітка: наведені нижче приклади є дійсними лише тому, що xніколи не брали їх адреси, тому вони є "подібними до реєстру". Вони також були б дійсними, якби тип xмав уявлення про пастки; це рідко буває для непідписаних типів (для цього потрібно «витратити» принаймні один біт сховища, і це має бути задокументовано), і це неможливо unsigned char. Якби xмав тип зі знаком, тоді реалізація могла б визначити бітовий шаблон, який не є числом від - (2 n-1 -1) до 2 n-1 -1, як представлення пастки. Дивіться відповідь Єнса Густедта .

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

Коли рядок 3 обчислюється, він xще не ініціалізований, тому (з причини компілятора) рядок 3 повинен бути якоюсь випадковістю, яка не може відбутися через інші умови, які компілятор не був достатньо розумним, щоб зрозуміти. Оскільки zне використовується після рядка 4 і xне використовується перед рядком 5, один і той же регістр може використовуватися для обох змінних. Отже, ця невеличка програма компілюється до наступних операцій над регістрами:

Кінцеве значення x- це кінцеве значення r0, а кінцеве значення y- це кінцеве значення r1. Ці значення складають x = -3 та y = -4, а не 5 та 4, як це сталося б, xякби було належним чином ініціалізовано.

Для більш детального прикладу розглянемо такий фрагмент коду:

Припустимо, що компілятор виявляє, що conditionне має побічних ефектів. Оскільки conditionне змінюється x, компілятор знає, що перший цикл циклу не може бути доступним, xоскільки він ще не ініціалізований. Тому перше виконання тіла циклу еквівалентно x = some_value(), немає необхідності перевіряти стан. Компілятор може скомпілювати цей код так, ніби ви написали

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

  • під час першої ітерації циклу xнеініціалізована до часу -xобчислення.
  • -x має невизначену поведінку, тому його значення є чим завгодно-зручним.
  • Застосовується правило оптимізації , тому цей код можна спростити .condition ? value : valuecondition; value

Зіткнувшись з кодом у вашому запитанні, цей самий компілятор аналізує, що коли x = - xобчислюється, значення -xмає те, що є зручним. Тож призначення можна оптимізувати.

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

Цей гіпотетичний компілятор відображає кожну змінну на іншій сторінці пам'яті та встановлює атрибути сторінки таким чином, що читання з неініціалізованої змінної спричиняє пастку процесора, яка викликає налагоджувач. Будь-яке призначення змінної спочатку гарантує, що її сторінка пам’яті нормально відображена. Цей компілятор не намагається виконати будь-яку розширену оптимізацію - він перебуває в режимі налагодження, призначеному для легкого пошуку таких помилок, як неініціалізовані змінні. При x = - xобчисленні права частина викликає пастку, і налагоджувач спрацьовує.


+1 Гарне пояснення, стандарт особливо дбає про цю ситуацію. Продовження цієї історії див. У моїй відповіді нижче. (занадто довго, щоб мати коментар).
Йенс Густедт

@JensGustedt О, ваша відповідь робить дуже важливий момент, який я (та інші) пропустив: якщо тип не має значень trap, що для непідписаного типу вимагає "витрачати" хоча б один біт, xмає неініціалізоване значення, але поведінка при доступі бути визначеним, якщо x не мав поведінки, подібної до реєстру.
Жиль 'ТАК - перестань бути злим'

@Gilles: принаймні clang робить ті види оптимізації, про які ви згадали: (1) , (2) , (3) .
Влад

1
Яка практична перевага полягає у тому, щоб кланг обробляв речі таким чином? Якщо в низхідному коді ніколи не використовується значення x, тоді всі операції з ним можуть бути опущені, незалежно від того, було визначено його значення чи ні. Якщо код, що слідує, наприклад if (volatile1) x=volatile2; ... x = (x+volatile3) & 255;, буде однаково задоволений будь-яким значенням 0-255, яке xможе містити у випадку, коли volatile1він дав нуль, я думаю, реалізація, яка дозволить програмісту пропустити непотрібний запис, xслід розглядати як вищу якість, ніж та, яка поводився б ...
supercat

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

16

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

(6.2.6.1 на пізньому проекті C11 говорить) Деякі подання об'єктів не повинні представляти значення типу об'єкта. Якщо збережене значення об'єкта має таке представлення і читається виразом lvalue, який не має символьного типу, поведінка не визначена. Якщо таке подання створюється побічним ефектом, який змінює весь або будь-яку частину об'єкта за допомогою виразу lvalue, який не має типу символу, поведінка не визначена. 50) Таке представлення називається представленням пастки.

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


3
@VladLazarenko: Йдеться про C, а не про конкретні процесори. Будь-хто може тривіально розробити процесор, який має розрядні шаблони для цілих чисел, що зводить його з розуму. Розглянемо центральний процесор, який у своїх реєстрах має "божевільний біт".
Девід Шварц,

2
То чи можу я тоді сказати, що поведінка чітко визначена у випадку цілих чисел і x86?

3
Ну, теоретично у вас може бути компілятор, який вирішив використовувати лише 28-бітові цілі числа (на x86) і додати конкретний код для обробки кожного додавання, множення (і так далі) і переконатися, що ці 4 біти залишаються невикористаними (або видавати SIGSEGV інакше ). Це може спричинити неініціалізоване значення.
ек-

4
Я ненавиджу, коли хтось ображає всіх інших, бо хтось не розуміє питання. Чи невизначена поведінка - це цілком питання того, що говорить стандарт. О, і в сценарії еквалайзера немає нічого практичного ... він цілком надуманий.
Jim Balter

7
@Vlad Lazarenko: Процесори Itanium мають прапорець NaT (Not Thing) для кожного цілочисельного реєстру. Прапор NaT використовується для управління спекулятивним виконанням і може затримуватися в регістрах, які не були належним чином ініціалізовані перед використанням. Зчитування з такого реєстру з набором бітів NaT дає виняток. Дивіться blogs.msdn.com/b/oldnewthing/archive/2004/01/19/60162.aspx
Північний мейнфрейм

13

(Ця відповідь стосується C 1999. Щодо C 2011, див. Відповідь Єнса Густедта.)

Стандарт C не говорить, що використання значення об'єкта автоматичної тривалості зберігання, яке не ініціалізоване, є невизначеною поведінкою. Стандарт C 1999 в 6.7.8 10 говорить: "Якщо об'єкт, що має автоматичну тривалість зберігання, не ініціалізується явно, його значення невизначене". (Цей параграф продовжує визначати спосіб ініціалізації статичних об'єктів, тому єдиними неініціалізованими об'єктами, які нас турбують, є автоматичні об'єкти.)

3.17.2 визначає „невизначене значення” як „або невизначене значення, або подання пастки”. 3.17.3 визначає "неуточнене значення" як "дійсне значення відповідного типу, якщо цей міжнародний стандарт не встановлює вимог щодо того, яке значення обрано в будь-якому випадку".

Отже, якщо неініціалізований unsigned int xмає невизначене значення, тоді x -= xповинен вивести нуль. Це залишає питання, чи може це бути пасткою. Доступ до значення пастки справді спричиняє невизначену поведінку, згідно з 6.2.6.1 5.

Деякі типи об’єктів можуть мати уявлення про пастки, такі як сигнальні NaN чисел з плаваючою комою. Але цілі числа без знака є особливими. Згідно з 6.2.6.2, кожен з N бітів значення беззнакового int представляє ступінь 2, а кожна комбінація бітів значення представляє одне зі значень від 0 до 2 N -1. Отже, цілі числа без знака можуть мати уявлення про перехоплення лише завдяки деяким значенням у їх бітах доповнення (наприклад, біт парності).

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


Якщо xмає уявлення про пастку, то x -= xможе затримати, чи не так? Тим не менше, +1 для вказівки на цілі числа без підпису без зайвих бітів повинен мати визначену поведінку - це явно протилежність інших відповідей, і (згідно з цитатою), здається, це те, що передбачає стандарт.
user541686

Так, якщо тип xмає представлення trap, тоді x -= xможе trap. Навіть просто xвикористання як значення може затримати. (Безпечно використовувати xяк значення lvalue; запис на об’єкт не впливатиме на представлення пастки, що знаходиться в ньому.)
Ерік Постпішлі

непідписані типи рідко мають пастку
Єнс Гуштедт

Цитуючи Реймонда Чена , "На ia64 кожен 64-розрядний регістр насправді становить 65 біт. Додатковий біт називається" NaT ", що означає" не річ ". Біт встановлюється, коли регістр не містить дійсного значення. Подумайте про це як про цілочисельну версію NaN із плаваючою точкою ... якщо у вас є регістр, значення якого NaT, і ви так сильно дихаєте на нього (наприклад, спробуйте зберегти його значення в пам'яті), процесор викличе виняток STATUS_REG_NAT_CONSUMPTION ". Тобто, біт-пастка може бути зовсім поза значенням.
Вітаю і hth. - Альф

-1 Вираз "Якщо на вашій цільовій платформі непідписаний int не має бітів заповнення, то неініціалізований непідписаний int не може мати представлення пастки, а використання його значення не може спричинити невизначену поведінку." не розглядає схеми, такі як біти x64 NaT.
Вітаю і hth. - Альф

11

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

Очевидно, що компілятор міг просто використати все значення сміття, яке він вважав "зручним", всередині змінної, і це буде працювати за призначенням ... що поганого в такому підході?

Чому, на вашу думку, цього не відбувається? Це саме такий підхід. Компілятор не повинен змусити його працювати, але він не повинен змусити його вийти з ладу.


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

7
1) Звичайно, вони могли б. Але я не можу придумати жодного аргументу, який би покращив це. 2) Платформа знає, що на значення неініціалізованої пам’яті не можна покладатися, тому вона може змінювати її безкоштовно. Наприклад, він може нульовувати неініціалізовану пам’ять у фоновому режимі, щоб мати нульові сторінки, готові до використання за потреби. (Поміркуйте, якщо це трапиться: 1) Ми читаємо значення для віднімання, скажімо, отримуємо 3. 2) Сторінка обнуляється, оскільки вона не ініціалізована, змінюючи значення на 0. 3) Ми робимо атомне віднімання, виділяючи сторінку та роблячи значення -3. На жаль.)
Девід Шварц,

2
-1, оскільки ви взагалі не обґрунтовуєте свою претензію. Бувають ситуації, коли було б вірно розраховувати, що компілятор просто приймає значення, записане в розташуванні пам'яті.
Йенс Густедт

1
@JensGustedt: Я не розумію твого коментаря. Ви можете пояснити?
Девід Шварц,

3
Тому що ви просто стверджуєте, що існує загальне правило, не посилаючись на нього. Як такий, це просто спроба "довести владою", що я не очікую від SO. І за неефективне аргументування, чому це не може бути неспецифічною цінністю. Єдина причина, що це UB у загальному випадку, полягає в тому, що це xможе бути оголошено як register, тобто його адреса ніколи не береться. Я не знаю, чи знали ви про це (якщо, ви це ефективно приховували), але у правильній відповіді про це має бути згадка.
Йенс Густедт

7

Для будь-якої змінної будь-якого типу, яка не ініціалізована або з інших причин містить невизначене значення, для коду, що читає це значення, застосовується таке:

  • Якщо змінна має автоматичну тривалість зберігання і не бере свою адресу, код завжди викликає невизначену поведінку [1].
  • В іншому випадку, якщо система підтримує уявлення про пастки для даного типу змінної, код завжди викликає невизначену поведінку [2].
  • В іншому випадку, якщо немає поданих зображень, змінна приймає невизначене значення. Немає гарантії, що це невизначене значення узгоджується кожного разу, коли читається змінна. Однак воно гарантовано не буде представленням пастки, і тому гарантовано не викликає невизначеної поведінки [3].

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


[1]: C11 6.3.2.1:

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

[2]: C11 6.2.6.1:

Певні представлення об'єктів не обов'язково представляють значення типу об'єкта. Якщо збережене значення об'єкта має таке представлення і читається виразом lvalue, який не має символьного типу, поведінка не визначена. Якщо таке подання створюється побічним ефектом, який змінює весь або будь-яку частину об'єкта за допомогою виразу lvalue, який не має типу символу, поведінка не визначена. 50) Таке представлення називається представленням пастки.

[3] C11:

3.19.2
невизначене значення
або невизначене значення, або представлення пастки

3.19.3
невстановлене значення
дійсне значення відповідного типу, коли цей міжнародний стандарт не встановлює вимог, щодо якого значення в будь-якому випадку вибирається
ПРИМІТКА. Невстановлене значення не може бути поданням пастки.

3.19.4
представлення пастки
об'єктне представлення, яке не обов'язково представляє значення типу об'єкта


3
@Vality У реальному світі 99,9999% усіх комп’ютерів - це два комплектуючі центральні процесори без зображень пастки. Тому жодне уявлення про пастки не є нормою, і обговорення поведінки на таких реальних комп'ютерах є надзвичайно актуальним. Припускати, що надзвичайно екзотичні комп’ютери є нормою, не корисно. Репрезентації пасток у реальному світі настільки рідкісні, що присутність терміна пастка репрезентація у стандарті переважно слід розглядати як стандартний дефект, успадкований від 1980-х років. Так само як і підтримка комп’ютерів, що доповнюють і підписують та змінюють величину.
Лундін

3
До речі, це відмінна причина, чому stdint.hзавжди слід використовувати замість власних типів C. Оскільки stdint.hзастосовується доповнення 2 і відсутні біти заповнення. Іншими словами, stdint.hтипи не можуть бути повноцінні.
Лундін

2
Знову відповідь комітету на звіт про дефект говорить: "Відповідь на питання 2 полягає в тому, що будь-яка операція, виконана з невизначеними значеннями, матиме в результаті невизначене значення". та "Відповідь на запитання 3 полягає в тому, що функції бібліотеки виявлятимуть невизначену поведінку при використанні з невизначеними значеннями."
Antti Haapala,

2
DRs 451 та 260
Antti Haapala

1
@AnttiHaapala Так, я знаю про цього ДР. Це не суперечить цій відповіді. Ви можете отримати невизначене значення при читанні неініціалізованого місця пам'яті, і це не обов'язково кожного разу одне і те ж значення. Але це невизначена поведінка, а не невизначена поведінка.
Лундін,

2

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

компілятор для такої платформи, як ARM, де всі інструкції, крім навантажень і сховищ, працюють на 32-розрядних регістрах, можуть обґрунтовано обробляти код способом, еквівалентним:

Якщо будь-яке летке читання дає ненульове значення, r0 завантажується зі значенням в діапазоні 0 ... 65535. В іншому випадку це дасть все, що було у нього під час виклику функції (тобто значення, передане в x), яке може не бути значенням в діапазоні 0..65535. У Стандарті відсутня будь-яка термінологія для опису поведінки значення, тип якого uint16_t, але значення якого знаходиться за межами діапазону 0..65535, за винятком того, що будь-яка дія, яка може спричинити таку поведінку, викликає UB.


Цікаво. То ви кажете, що прийнята відповідь неправильна? Або ви кажете, що це правильно в теорії, але на практиці укладачі можуть робити більш дивні речі?
user541686

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

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

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

@Mehrdad: Прийнята відповідь фокусується на архітектурах, регістри яких мають додатковий "неініціалізований" стан, і захоплюють, якщо завантажується неініціалізований регістр. Такі архітектури існують, але не є звичайними явищами. Я описую сценарій, коли звичайне обладнання може виявляти поведінку, яка виходить за рамки будь-чого, що передбачається стандартом C, але буде корисно обмежена, якщо компілятор не додасть свою власну додаткову хитрість до суміші. Наприклад, якщо функція має параметр, який вибирає операцію для виконання, а деякі операції повертають корисні дані, а інші - ні, ...
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.