Давайте подивимося, як стандарт С визначає терміни "поведінка" та "невизначена поведінка".
Посилання на проект N1570 стандарту ISO C 2011; Мені невідомі будь-які відповідні відмінності в будь-якому з трьох опублікованих стандартів ISO C (1990, 1999 та 2011).
Розділ 3.4:
поведінка
зовнішній вигляд або дія
Гаразд, це трохи розмито, але я б стверджував, що дане твердження не має "зовнішнього вигляду" і, звичайно, жодної "дії", якщо воно насправді не виконане.
Розділ 3.4.3:
невизначена поведінка
поведінки при використанні непереносимої або помилкової конструкції програми або помилкових даних, щодо яких цей міжнародний стандарт не вимагає
Там сказано " після використання " такої конструкції. Слово "використання" не визначене стандартом, тому ми повертаємось до загальноанглійського значення. Конструкція не "використовується", якщо вона ніколи не виконується.
Під цим визначенням є примітка:
ПРИМІТКА. Можлива невизначена поведінка варіюється від ігнорування ситуації повністю з непередбачуваними результатами, поведінки під час перекладу або виконання програми документально, характерним для середовища (з видачею діагностичного повідомлення або без нього), до припинення перекладу або виконання (із видача діагностичного повідомлення).
Отже, компілятору дозволено відхилити вашу програму під час компіляції, якщо її поведінка невизначена. Але моя інтерпретація цього полягає в тому, що він може це зробити лише в тому випадку, якщо зможе довести, що кожне виконання програми матиме невизначену поведінку. Що означає, я думаю, що це:
if (rand() % 2 == 0) {
i = i / 0;
}
який, безумовно, може мати невизначену поведінку, не може бути відхилений під час компіляції.
З практичної точки зору програми повинні мати можливість виконувати тести виконання під час виконання, щоб захиститись від невикликання невизначеної поведінки, і стандарт повинен дозволяти їм це робити.
Вашим прикладом було:
if (0) {
i = 1/0;
}
який ніколи не виконує ділення на 0. Дуже поширеною ідіомою є:
int x, y;
if (y != 0) {
x = x / y;
}
Ділення, безумовно, має невизначену поведінку if y == 0
, але ніколи не виконується if y == 0
. Поведінка чітко визначена і з тієї самої причини, що і ваш приклад чітко визначена: оскільки потенційна невизначена поведінка насправді ніколи не може відбутися.
(Хіба що INT_MIN < -INT_MAX && x == INT_MIN && y == -1
(так, цілочисельний поділ може переповнюватися), але це окрема проблема.)
У коментарі (після видалення) хтось зазначив, що компілятор може оцінювати константні вирази під час компіляції. Що правда, але не актуально в даному випадку, оскільки в контексті
i = 1/0;
1/0
не є постійним виразом .
Постійне вираз є синтаксичною категорією , яка зводиться до умовно-вираженню (яке НЕ включає завдання і вираз коми). Виробничий константний вираз з'являється в граматиці лише в контексті, який насправді потребує постійного виразу, наприклад, мітки регістру. Тож якщо ви пишете:
switch (...) {
case 1/0:
...
}
тоді 1/0
є константним виразом - і таким, який порушує обмеження в 6.6p4: "Кожен константний вираз має оцінювати константу, яка знаходиться в діапазоні репрезентативних значень для свого типу.", тому потрібна діагностика. Але для правої частини завдання не потрібен константний вираз , а лише умовний вираз , тому обмеження на константні вирази не застосовуються. Компілятор може оцінити будь-який вираз, який він здатний виконати під час компіляції, але лише якщо поведінка така ж, як якщо б вона була оцінена під час виконання (або, в контексті if (0)
, не обчислювалася під час виконання ().
(Те, що виглядає точно як константний вираз , не обов’язково є константним виразом , так само, як, в x + y * z
, послідовність x + y
не є адитивним виразом через контекст, в якому вона з’являється.)
Що означає виноску в розділі 6.6 N1570, яку я збирався процитувати:
Таким чином, у наступній ініціалізації
static int i = 2 || 1 / 0;
вираз є дійсним цілочисельним постійним виразом зі значенням один.
насправді не стосується цього питання.
Нарешті, є кілька речей, які визначено спричиняти невизначену поведінку, яка не стосується того, що відбувається під час виконання. Додаток J, розділ 2 стандарту С (знову ж, див. Проект N1570 ) перелічує речі, які спричиняють невизначену поведінку, зібрані з решти стандарту. Деякі приклади (я не стверджую, що це вичерпний перелік):
- Непорожній вихідний файл не закінчується символом нового рядка, якому не передує символ зворотної косої риски, або закінчується частковою ознакою попередньої обробки або коментарем
- Конкатенація маркера створює послідовність символів, що відповідає синтаксису універсального імені символу
- Символ, що не знаходиться в базовому наборі вихідних символів, зустрічається у вихідному файлі, за винятком ідентифікатора, константи символів, рядкового літералу, імені заголовка, коментаря або маркера попередньої обробки, який ніколи не перетворюється на маркер
- Ідентифікатор, коментар, літеральний рядок, символьна константа або назва заголовка містить недійсний багатобайтовий символ або не починається і не закінчується у початковому стані зсуву
- Один і той самий ідентифікатор має як внутрішній, так і зовнішній зв'язок в одному блоці перекладу
Ці конкретні випадки - це речі, які компілятор може виявити. Я думаю, що їх поведінка невизначена, тому що комітет не хотів або не міг нав'язати однакову поведінку всім реалізаціям, а визначення діапазону дозволеної поведінки просто не вартувало зусиль. Вони насправді не підпадають під категорію "коду, який ніколи не буде виконаний", але я згадую їх тут для повноти.