Не визначена, не визначена та визначена реалізацією поведінка


530

Що таке невизначена поведінка в C і C ++? Що щодо не визначеної поведінки та визначеної реалізацією поведінки? У чому різниця між ними?


1
Я був майже впевнений, що ми провели цей день, але не можу його знайти. Дивіться також: stackoverflow.com/questions/2301372 / ...
dmckee --- Екс-модератор кошеня



1
Ось цікава дискусія (розділ «Додаток L та не визначена поведінка»).
Оуен

Відповіді:


405

Невизначена поведінка - один із тих аспектів мови C та C ++, який може здивувати програмістів, які приходять з інших мов (інші мови намагаються приховати це краще). В основному, можна писати програми C ++, які не ведуть себе передбачувано, хоча багато компіляторів C ++ не повідомлять про помилки в програмі!

Давайте розглянемо класичний приклад:

#include <iostream>

int main()
{
    char* p = "hello!\n";   // yes I know, deprecated conversion
    p[0] = 'y';
    p[5] = 'w';
    std::cout << p;
}

Змінна pвказує на рядковий літерал "hello!\n", і два завдання нижче, намагаються змінити цей літеральний рядок. Що робить ця програма? Згідно з пунктом 11 розділу 2.14.5 стандарту C ++, він посилається на невизначену поведінку :

Ефект спроби змінити літеральний рядок не визначений.

Я чую, як люди кричать "Але зачекайте, я можу скласти це без проблем і отримати вихід yellow" або "Що ви маєте на увазі невизначені, рядкові літерали зберігаються в пам'яті лише для читання, тому перша спроба призначення приводить до основного дампа". Це саме проблема невизначеної поведінки. В основному, стандарт дозволяє будь-що статися, коли ви посилаєтесь на невизначене поведінку (навіть носові демони). Якщо є «правильна» поведінка відповідно до вашої ментальної моделі мови, ця модель просто неправильна; Стандарт C ++ має єдиний період голосування.

Інші приклади невизначеної поведінки включають доступ до масиву за його межами, перенаправлення нульового вказівника , доступ до об'єктів після закінчення їхнього життя або написання нібито розумних виразів на зразок i++ + ++i.

У розділі 1.9 стандарту С ++ також згадуються два менш небезпечні брати, не визначена поведінка та визначена реалізацією поведінка :

Семантичні описи цього Міжнародного стандарту визначають параметризовану недетерміновану абстрактну машину.

Деякі аспекти та операції абстрактної машини описані в цьому Міжнародному стандарті як визначені реалізацією (наприклад, sizeof(int)). Вони складають параметри абстрактної машини. Кожна реалізація повинна включати в себе документацію, що описує її характеристики та поведінку в цьому відношенні.

Деякі інші аспекти та операції абстрактної машини описані в цьому Міжнародному стандарті як невизначені (наприклад, порядок оцінки аргументів функції). Де можливо, цей Міжнародний стандарт визначає набір допустимих способів поведінки. Вони визначають недетерміновані аспекти абстрактної машини.

Деякі інші операції описані в цьому Міжнародному стандарті як невизначені (наприклад, ефект відсилання нульового вказівника). [ Примітка : цей Міжнародний стандарт не пред'являє жодних вимог до поведінки програм, що містять не визначену поведінку. - кінцева примітка ]

Зокрема, в розділі 1.3.24 зазначено:

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

Що ви можете зробити, щоб не стикатися з невизначеною поведінкою? В основному, ви повинні читати хороші книги на C ++ авторів, які знають, про що вони говорять. Накрутіть Інтернет-підручники. Гвинтовий бичок.


6
Це дивний факт, що виник у результаті злиття, що ця відповідь охоплює лише C ++, але теги цього питання включають C. C має інше поняття "невизначена поведінка": все одно буде потрібно впровадження для надання діагностичних повідомлень, навіть якщо про поведінку також заявлено бути невизначеним для певних порушень правил (порушення обмежень).
Йоханнес Шауб - ліб

8
@ Benoit Це невизначена поведінка, оскільки стандарт говорить, що це невизначена поведінка, період. У деяких системах дійсно рядкові літерали зберігаються в текстовому сегменті лише для читання, і програма вийде з ладу, якщо ви спробуєте змінити рядковий літерал. В інших системах рядковий літерал дійсно з’явиться зміною. Стандарт не передбачає, що має відбутися. Ось що означає невизначена поведінка.
fredoverflow

5
@FredOverflow, Чому хороший компілятор дозволяє нам компілювати код, який надає невизначене поведінку? Саме то , що добре може компіляції такого коду віддання? Чому не всі хороші компілятори дали нам величезний червоний попереджувальний знак, коли ми намагаємось скласти код, який дає невизначену поведінку?
Pacerier

14
@Pacerier Є певні речі, які не піддаються перевірці під час компіляції. Наприклад, не завжди можна гарантувати, що нульовий покажчик ніколи не буде відмежований, але це не визначено.
Тім Сегейн

4
@ Целеріти, невизначена поведінка може бути недетермінованою. Наприклад, неможливо знати заздалегідь, яким буде вміст неініціалізованої пам’яті, наприклад. int f(){int a; return a;}: значення aможе змінюватися між викликами функцій.
Марк

97

Ну, це в основному пряма копія-вставка зі стандарту

3.4.1 1 не визначена поведінка, визначена поведінкою, де кожна реалізація документує спосіб вибору

2 ПРИКЛАД Прикладом поведінки, визначеної реалізацією, є поширення біта високого порядку, коли підписане ціле число зміщується вправо.

3.4.3 1 не визначена поведінкова поведінка при використанні неподатної або помилкової побудови програми або помилкових даних, до яких цей Міжнародний стандарт не пред'являє жодних вимог

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

3 ПРИКЛАД Прикладом невизначеної поведінки є поведінка при цілому переповненні.

3.4.4 1 невказане використання поведінки невизначеного значення або інша поведінка, коли цей Міжнародний стандарт надає дві або більше можливостей і не пред'являє жодних додаткових вимог, щодо яких вибирається в будь-якому випадку

2 ПРИКЛАД Прикладом неуточненої поведінки є порядок оцінювання аргументів функції.


3
Яка різниця між визначеною реалізацією та не визначеною поведінкою?
Золомон

26
@Zolomon: Так само, як це сказано: в основному те саме, за винятком того, що в разі визначеної реалізацією реалізації потрібно задокументувати (гарантувати), що саме відбуватиметься, тоді як у випадку невказаного виконання не потрібно документувати або гарантувати що-небудь.
ANT

1
@Zolomon: Це відображається в різниці між 3.4.1 і 2.4.4.
sbi

8
@Celeritas: Гіперсучасні компілятори можуть зробити краще. Даний int foo(int x) { if (x >= 0) launch_missiles(); return x << 1; }компілятор може визначити, що оскільки всі засоби виклику функції, яка не запускає ракети, викликають Undefined Behavior, він може зробити виклик launch_missiles()безумовним.
supercat

2
@northerner Як зазначено у цитаті, невказана поведінка зазвичай обмежується обмеженим набором можливих поведінок. У деяких випадках ви навіть можете прийти до висновку, що всі ці можливості є прийнятними в даному контексті, в яких випадки неуточнена поведінка зовсім не є проблемою. Не визначена поведінка абсолютно не обмежена (наприклад, "програма може вирішити відформатувати ваш жорсткий диск"). Не визначена поведінка - це завжди проблема.
ANT

60

Можливо, легке формулювання може бути простішим для розуміння, ніж суворе визначення стандартів.

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

невизначена поведінка
Ви робите щось не так. Наприклад, у вас дуже велике значення у intтому, що не вписується char. Як ви ставите це значення char? насправді немає способу! Будь-що може статися, але найрозумнішим було б взяти перший байт цього інта і вкласти його char. Це неправильно робити це, щоб призначити перший байт, але ось що відбувається під кришкою.

неуточнена поведінка
Яка функція цих двох виконується спочатку?

void fun(int n, int m);

int fun1()
{
  cout << "fun1";
  return 1;
}
int fun2()
{
  cout << "fun2";
  return 2;
}
...
fun(fun1(), fun2()); // which one is executed first?

Мова не визначає оцінку, зліва направо чи справа наліво! Таким чином, невизначена поведінка може спричинити або не призвести до невизначеної поведінки, але, безумовно, ваша програма не повинна створювати не визначене поведінку.


@eSKay Я думаю, що ваше питання варто відредагувати відповідь, щоб уточнити більше :)

для fun(fun1(), fun2());чи не визначено поведінку "впровадження"? Зрештою, компілятор повинен вибрати той чи інший курс?

Різниця між визначеною реалізацією та не визначеною, полягає в тому, що компілятор повинен вибирати поведінку в першому випадку, але в другому випадку це не обов'язково. Наприклад, реалізація повинна мати одне і лише одне визначення sizeof(int). Отже, не можна сказати, що sizeof(int)це 4 для однієї частини програми та 8 для інших. На відміну від невизначеної поведінки, де компілятор може сказати ОК, я буду оцінювати ці аргументи зліва направо, а аргументи наступної функції оцінюються справа наліво. Це може статися в одній програмі, тому її називають не визначеною . Насправді, C ++ могло бути полегшеним, якби було визначено деякі невизначені форми поведінки. Подивіться тут на відповідь доктора Струструпа на це :

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

У ІМО занадто багато "речей" не визначено, не визначено, визначено реалізацією тощо. Однак це легко сказати і навіть навести приклади, але важко виправити. Слід також зазначити, що не все так складно уникнути більшості проблем і створити портативний код.


1
бо fun(fun1(), fun2());не поведінка "implementation defined"? Зрештою, компілятор повинен вибрати той чи інший курс?
Лазер

1
@AraK: дякую за пояснення. Я зараз це розумію. До речі, "I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left"я розумію, що це canвідбувається. Це справді, з компіляторами, якими ми користуємося сьогодні?
Лазер

1
@eSKay Ви повинні запитати у цього гуру, який забруднив руки багатьма компіляторами :) AFAIK VC завжди оцінює аргументи справа наліво.
AraK

4
@Lazer: Це точно може статися. Простий сценарій: foo (bar, boz ()) і foo (boz (), bar), де bar є int, а boz () - функція, що повертає int. Припустимо CPU, де очікується передача параметрів у регістри R0-R1. Результати функції повертаються в R0; функції можуть скинути R1. Оцінка "бар" перед "boz ()" вимагає збереження копії бару десь ще до виклику boz (), а потім завантаження збереженої копії. Оцінка "бар" після "boz ()" дозволить уникнути зберігання пам'яті та повторного вибору, і це оптимізація, яку багато компілятори будуть робити незалежно від їх порядку в списку аргументів.
supercat

6
Я не знаю про C ++, але стандарт C говорить про те, що перетворення int в char - це або визначена реалізація, або навіть чітко визначена (залежно від фактичних значень та підписаності типів). Див. С99 §6.3.1.3 (без змін у С11).
Микола Рухе

27

З офіційного документа з обґрунтування С

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

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

Невизначена поведінка дає виконавцю ліцензію не вловлювати певні програмні помилки, які важко діагностувати. Він також визначає області можливого відповідного розширення мови: реалізатор може доповнити мову, надаючи визначення офіційно невизначеної поведінки.

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


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

2
Ще один момент, який я щойно помітив: C89 не використовував термін "розширення" для опису функцій, які були гарантовані для деяких реалізацій, але не для інших. Автори C89 визнали, що більшість тодішніх реалізацій буде трактувати підписану арифметичну та непідписану арифметику однаково, за винятком випадків, коли результати використовуються певним чином, і таке лікування застосовується навіть у випадку підписаного переповнення; вони не перераховували це, як загальне продовження у Додатку J2, однак, що мені підказує, вони розглядали це як природний стан справ, а не як продовження.
supercat

10

Невизначена поведінка проти невизначеної поведінки має її короткий опис.

Підсумковий підсумок:

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


1
Існують два види компіляторів: ті, які, якщо явно не зафіксовано інше, інтерпретують більшість форм Невизначеного поведінки як такі, що відступають від характерних форм поведінки, зафіксованих базовим середовищем, і ті, які за замовчуванням лише корисно розкривають поведінку, яку Стандарт характеризує як Визначення впровадження Використовуючи компілятори першого типу, багато речей першого типу можна зробити ефективно та безпечно за допомогою UB. Компілятори для другого типу підходять лише для таких завдань, якщо вони нададуть варіанти гарантування поведінки в таких випадках.
supercat

8

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

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

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

Наприклад, вказаний наступний код:

int scaled_velocity(int v, unsigned char pow)
{
  if (v > 250)
    v = 250;
  if (v < -250)
    v = -250;
  return v << pow;
}

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

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


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

Але той факт, що <<UB має негативні цифри, - це дуже неприємна пастка, і я радий про це нагадати!
Том Свірлі

1
@TomSwirly: На жаль, автори компілятора не переймаються тим, що пропонування вільних гарантій поведінки, що перевищують норми, передбачені Стандартом, часто може дозволити масштабне збільшення швидкості порівняно з тим, що вимагає, щоб цей код уникав будь-якої ціни нічого, що не визначено Стандартом. Якщо програмісту не важливо, чи буде i+j>k1 або 0 у випадках, коли додавання переливається за умови, що у нього немає інших побічних ефектів , компілятор, можливо, зможе зробити деякі масові оптимізації, які були б неможливі, якби програміст записав код як (int)((unsigned)i+j) > k.
supercat

1
@TomSwirly: Для них, якщо компілятор X може взяти строго відповідну програму, щоб виконати якусь задачу T і отримати виконувану програму, яка на 5% ефективніша, ніж компілятор Y, вийде з цією ж програмою, це означає, що X є кращим, навіть якщо Y може генерувати код, який виконував те саме завдання втричі ефективніше, даючи програму, яка використовує поведінку, яку гарантує Y, але X - ні.
supercat

6

Стандарт C ++ n3337 § 1.3.10 поведінка, визначена реалізацією

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

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


C ++ стандарт n3337 § 1.3.24 невизначена поведінка

поведінка, щодо якої цей Міжнародний стандарт не пред'являє жодних вимог [Примітка: Не визначена поведінка може очікуватися, коли цей Міжнародний стандарт не містить явного визначення поведінки або коли програма використовує помилкову конструкцію або помилкові дані. Допустима невизначена поведінка варіюється від ігнорування ситуації повністю з непередбачуваними результатами, до поведінки під час перекладу чи виконання програми в документально підтвердженому для середовища середовищі (з видачею діагностичного повідомлення або без нього), до припинення перекладу чи виконання (з видачею діагностичного повідомлення). Багато помилкових програмних конструкцій не породжують невизначеної поведінки; їх вимагають діагностувати. - кінцева примітка]

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


C ++ стандарт n3337 § 1.3.25 не визначена поведінка

поведінка, для добре сформованої побудови програми та коректних даних, що залежить від реалізації [Примітка. Реалізація не потрібна для документального підтвердження поведінки. Діапазон можливих форм поведінки зазвичай визначається цим Міжнародним стандартом. - кінцева примітка]

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


6

Впровадження визначено-

Виконавці бажають, повинні бути добре задокументовані, стандарт дає вибір, але обов'язково збирає

Не визначено -

Те саме, що визначено реалізацією, але не задокументовано

Не визначено-

Що б не сталося, подбайте про це.


2
Я думаю, що важливо зазначити, що практичне значення «невизначеного» змінилося за останні кілька років. Раніше це було дано uint32_t s;, оцінюючи, 1u<<sколи s33 роки, можна очікувати, що, можливо, вийде 0, а може бути, і вихід 2, але нічого іншого дурне не робити. Однак новіші компілятори, що оцінюють, 1u<<sможуть змусити компілятора визначити, що оскільки раніше sповинно було бути менше 32, будь-який код до цього виразу, або після нього, sможе бути опущений лише тоді, коли було 32 або більше, може бути опущений.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.