Що таке невизначена поведінка в C і C ++? Що щодо не визначеної поведінки та визначеної реалізацією поведінки? У чому різниця між ними?
Що таке невизначена поведінка в C і C ++? Що щодо не визначеної поведінки та визначеної реалізацією поведінки? У чому різниця між ними?
Відповіді:
Невизначена поведінка - один із тих аспектів мови 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 ++ авторів, які знають, про що вони говорять. Накрутіть Інтернет-підручники. Гвинтовий бичок.
int f(){int a; return a;}
: значення a
може змінюватися між викликами функцій.
Ну, це в основному пряма копія-вставка зі стандарту
3.4.1 1 не визначена поведінка, визначена поведінкою, де кожна реалізація документує спосіб вибору
2 ПРИКЛАД Прикладом поведінки, визначеної реалізацією, є поширення біта високого порядку, коли підписане ціле число зміщується вправо.
3.4.3 1 не визначена поведінкова поведінка при використанні неподатної або помилкової побудови програми або помилкових даних, до яких цей Міжнародний стандарт не пред'являє жодних вимог
2 ПРИМІТКА Можлива невизначена поведінка варіюється від ігнорування ситуації повністю з непередбачуваними результатами, до поведінки під час перекладу або виконання програми в документально підтвердженому для середовища середовищі (з видачею діагностичного повідомлення або без нього), до припинення перекладу чи виконання (з видача діагностичного повідомлення).
3 ПРИКЛАД Прикладом невизначеної поведінки є поведінка при цілому переповненні.
3.4.4 1 невказане використання поведінки невизначеного значення або інша поведінка, коли цей Міжнародний стандарт надає дві або більше можливостей і не пред'являє жодних додаткових вимог, щодо яких вибирається в будь-якому випадку
2 ПРИКЛАД Прикладом неуточненої поведінки є порядок оцінювання аргументів функції.
int foo(int x) { if (x >= 0) launch_missiles(); return x << 1; }
компілятор може визначити, що оскільки всі засоби виклику функції, яка не запускає ракети, викликають Undefined Behavior, він може зробити виклик launch_missiles()
безумовним.
Можливо, легке формулювання може бути простішим для розуміння, ніж суворе визначення стандартів.
поведінка, визначена реалізацією
Мова говорить про те, що у нас є типи даних. Постачальники компілятора вказують, які розміри вони повинні використовувати, та надають документацію про те, що вони робили.
невизначена поведінка
Ви робите щось не так. Наприклад, у вас дуже велике значення у 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 ++. Так само не визначений порядок оцінки аргументів.
У ІМО занадто багато "речей" не визначено, не визначено, визначено реалізацією тощо. Однак це легко сказати і навіть навести приклади, але важко виправити. Слід також зазначити, що не все так складно уникнути більшості проблем і створити портативний код.
fun(fun1(), fun2());
не поведінка "implementation defined"
? Зрештою, компілятор повинен вибрати той чи інший курс?
"I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left"
я розумію, що це can
відбувається. Це справді, з компіляторами, якими ми користуємося сьогодні?
З офіційного документа з обґрунтування С
Терміни неуточнена поведінка, невизначена поведінка та визначена реалізацією поведінка використовуються для категоризації результатів написання програм, властивості яких Стандарт не містить або не може повністю описати. Метою прийняття цієї категоризації є дозволити певному розмаїттю серед впроваджень, що дозволяє якості впровадження бути активною силою на ринку, а також дозволити певні популярні розширення, не виймаючи кеш відповідності Стандарту. Додаток F до стандарту каталогізує поведінку, що належить до однієї з цих трьох категорій.
Невизначена поведінка надає виконавцеві деяку широту в перекладі програм. Ця широта не поширюється, наскільки не вдалося перекласти програму.
Невизначена поведінка дає виконавцю ліцензію не вловлювати певні програмні помилки, які важко діагностувати. Він також визначає області можливого відповідного розширення мови: реалізатор може доповнити мову, надаючи визначення офіційно невизначеної поведінки.
Поведінка, визначена реалізацією, дає виконавцю свободу вибору відповідного підходу, але вимагає, щоб цей вибір був пояснений користувачеві. Поведінки, визначені як визначені реалізацією, як правило, є тими, в яких користувач може приймати змістовні рішення щодо кодування на основі визначення реалізації. Виконавці повинні мати на увазі цей критерій, приймаючи рішення про те, якою великою має бути визначення реалізації. Як і у випадку невизначеної поведінки, просто невдача перекладу джерела, що містить визначену реалізацією поведінку, не є адекватною відповіддю.
Невизначена поведінка проти невизначеної поведінки має її короткий опис.
Підсумковий підсумок:
Підводячи підсумок, невизначена поведінка - це те, про що ви не повинні турбуватися, якщо тільки ваше програмне забезпечення не вимагає переносності. І навпаки, невизначена поведінка завжди небажана і ніколи не має відбуватися.
Історично і поведінка, визначена реалізацією, і не визначена поведінка представляли ситуації, в яких автори Стандарту очікували, що люди, які пишуть якісні реалізації, будуть використовувати судження, щоб вирішити, які гарантії поведінки, якщо такі є, будуть корисні для програм у передбачуваному полі застосування, що працює на цільові цілі. Потреби в висококласному коді скорочення чисел дуже відрізняються від вимог системного коду низького рівня, і 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 має негативні цифри, - це дуже неприємна пастка, і я радий про це нагадати!
i+j>k
1 або 0 у випадках, коли додавання переливається за умови, що у нього немає інших побічних ефектів , компілятор, можливо, зможе зробити деякі масові оптимізації, які були б неможливі, якби програміст записав код як (int)((unsigned)i+j) > k
.
Стандарт C ++ n3337 § 1.3.10 поведінка, визначена реалізацією
поведінка, для добре сформованої програми побудувати та виправити дані, що залежить від реалізації та кожного документа на реалізацію
Іноді стандарт C ++ не нав'язує особливу поведінку деяким конструкціям, але натомість говорить, що певна, чітко визначена поведінка повинна бути обрана та описана конкретною реалізацією (версія бібліотеки). Таким чином, користувач все ще може точно знати, як буде вести себе програма, навіть якщо Standard цього не описує.
C ++ стандарт n3337 § 1.3.24 невизначена поведінка
поведінка, щодо якої цей Міжнародний стандарт не пред'являє жодних вимог [Примітка: Не визначена поведінка може очікуватися, коли цей Міжнародний стандарт не містить явного визначення поведінки або коли програма використовує помилкову конструкцію або помилкові дані. Допустима невизначена поведінка варіюється від ігнорування ситуації повністю з непередбачуваними результатами, до поведінки під час перекладу чи виконання програми в документально підтвердженому для середовища середовищі (з видачею діагностичного повідомлення або без нього), до припинення перекладу чи виконання (з видачею діагностичного повідомлення). Багато помилкових програмних конструкцій не породжують невизначеної поведінки; їх вимагають діагностувати. - кінцева примітка]
Коли програма стикається з конструкцією, яка не визначена відповідно до стандарту C ++, їй дозволяється робити все, що хоче робити (можливо, надіслати мені електронний лист або, можливо, надішле вам електронний лист або, можливо, повністю проігнорує код).
C ++ стандарт n3337 § 1.3.25 не визначена поведінка
поведінка, для добре сформованої побудови програми та коректних даних, що залежить від реалізації [Примітка. Реалізація не потрібна для документального підтвердження поведінки. Діапазон можливих форм поведінки зазвичай визначається цим Міжнародним стандартом. - кінцева примітка]
Стандарт C ++ не нав'язує особливу поведінку деяким конструкціям, але натомість каже, що конкретну, чітко визначену поведінку потрібно вибирати ( бот не потрібно описувати ) конкретною реалізацією (версія бібліотеки). Так, у випадку, коли жодного опису не надано, користувачеві може бути важко точно знати, як буде вести себе програма.
Впровадження визначено-
Виконавці бажають, повинні бути добре задокументовані, стандарт дає вибір, але обов'язково збирає
Не визначено -
Те саме, що визначено реалізацією, але не задокументовано
Не визначено-
Що б не сталося, подбайте про це.
uint32_t s;
, оцінюючи, 1u<<s
коли s
33 роки, можна очікувати, що, можливо, вийде 0, а може бути, і вихід 2, але нічого іншого дурне не робити. Однак новіші компілятори, що оцінюють, 1u<<s
можуть змусити компілятора визначити, що оскільки раніше s
повинно було бути менше 32, будь-який код до цього виразу, або після нього, s
може бути опущений лише тоді, коли було 32 або більше, може бути опущений.