Ответ 1
Посмотрите, как стандарт C определяет термины "поведение" и "поведение undefined".
Ссылки на проект N1570 стандарта ISO C 2011; Мне неизвестны какие-либо существенные различия в любом из трех опубликованных стандартов ISO C (1990, 1999 и 2011 гг.).
Раздел 3.4:
поведение
внешний вид или действие
Хорошо, это немного расплывчато, но я бы утверждал, что данное утверждение не имеет "внешнего вида" и, конечно, никакого "действия", если только оно не выполняется.
Раздел 3.4.3:
undefined поведение
поведение, при использовании непереносимой или ошибочной программы или ошибочных данных, для которых настоящий международный стандарт не предъявляет требований
В нем говорится "при использовании" такой конструкции. Слово "использование" не определяется стандартом, поэтому мы возвращаемся к общему английскому значению. Конструкция не используется, если она никогда не выполнялась.
В этом определении есть примечание:
ПРИМЕЧАНИЕ. Возможное поведение undefined варьируется от игнорирования ситуации полностью с непредсказуемыми результатами, вести себя во время перевода или выполнения программы документированным образом, характерным для (с выдачей диагностического сообщения или без него), чтобы прекращение перевода или исполнения (с выдачей диагностическое сообщение).
Поэтому компилятору разрешено отклонять вашу программу во время компиляции, если ее поведение undefined. Но моя интерпретация этого заключается в том, что он может сделать это только в том случае, если он может доказать, что каждое выполнение программы встретит поведение undefined. Это подразумевает, я думаю, что это:
if (rand() % 2 == 0) {
i = i / 0;
}
который, безусловно, может иметь поведение undefined, не может быть отклонен во время компиляции.
В практическом плане программы должны иметь возможность выполнять тесты времени выполнения для защиты от вызова поведения undefined, и стандарт должен позволить им это сделать.
Ваш пример:
if (0) {
i = 1/0;
}
который никогда не выполняет деление на 0. Очень распространенная идиома:
int x, y;
/* set values for x and y */
if (y != 0) {
x = x / y;
}
Разделение, конечно, имеет поведение undefined, если y == 0
, но оно никогда не выполняется, если y == 0
. Поведение четко определено и по той же причине, что ваш пример хорошо определен: поскольку потенциальное поведение undefined никогда не может произойти.
(Если 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;
выражение является допустимым целочисленным постоянным выражением со значением 1.
на самом деле не имеет отношения к этому вопросу.
Наконец, есть несколько вещей, которые определены, чтобы вызвать поведение undefined, которое не связано с тем, что происходит во время выполнения. Приложение J, раздел 2 стандарта C (снова см. Проект N1570) перечисляет вещи, которые вызывают поведение undefined, собранное из остальной части стандарта. Некоторые примеры (я не утверждаю, что это исчерпывающий список):
- Непустой исходный файл не заканчивается символом новой строки, которому сразу не предшествует символ обратной косой черты или заканчивается частичным токен предварительной обработки или комментарий
- Конкатенация токена создает последовательность символов, соответствующую синтаксису универсального символьного имени
- Символ, не содержащий базовый набор символов источника, встречается в исходном файле, за исключением идентификатора, символьной константы, строки литерал, имя заголовка, комментарий или токен предварительной обработки, который является никогда не преобразовывается в токен
- Идентификатор, комментарий, строковый литерал, символьная константа или имя заголовка содержит недопустимый многобайтовый символ или не начинается и заканчивается в начальном состоянии сдвига
- Тот же идентификатор имеет как внутреннюю, так и внешнюю связь в одной и той же единице перевода
Эти конкретные случаи - это вещи, которые мог обнаружить компилятор. Я думаю, что их поведение undefined, потому что комитет не хотел или не мог навязывать такое же поведение во всех реализациях, а определение диапазона разрешенных действий просто не стоило усилий. Они действительно не относятся к категории "кода, который никогда не будет выполнен", но я упоминаю их здесь для полноты.