В С++ 11 это Undefined Поведение, но в C это то, что while(1);
есть Undefined Поведение?
Показывается (1); undefined поведение в C?
Ответ 1
Это четко определенное поведение. В C11 добавлен новый пункт 6.8.5 ad 6
Оператор итерации, контрольное выражение которого не является постоянным выражением, 156) который не выполняет операций ввода-вывода, не получает доступ к неустойчивым объектам и не выполняет никаких операций синхронизации или атома в своем теле, выражение или (в случае оператора for) его выражение-3 может быть реализовано при завершении реализации. 157)
157) Предполагается разрешить преобразования компилятора, такие как удаление пустых циклов, даже если завершение невозможно доказать.
Поскольку управляющее выражение вашего цикла является константой, компилятор не может считать цикл завершенным. Это предназначено для реактивных программ, которые должны выполняться вечно, как операционная система.
Однако для следующего цикла поведение неясно
a = 1; while(a);
Фактически компилятор может или не может удалить этот цикл, в результате чего программа, которая может завершиться или не завершиться. Это не действительно undefined, так как не разрешено стирать ваш жесткий диск, но это конструкция, которой следует избегать.
Однако есть еще одна загвоздка, рассмотрим следующий код:
a = 1; while(a) while(1);
Теперь, поскольку компилятор может предположить, что внешний цикл завершается, внутренний цикл также должен заканчиваться, как иначе внешний контур завершается. Поэтому, если у вас есть действительно умный компилятор, тогда цикл while(1);
, который не должен заканчиваться, должен иметь такие бесконечные петли вокруг него вплоть до main
. Если вам действительно нужен бесконечный цикл, вам лучше прочитать или написать в нем переменную volatile
.
Почему этот пункт не практичен
Очень маловероятно, что наша компания-компилятор будет использовать этот пункт, главным образом потому, что это очень синтаксическое свойство. В промежуточном представлении (IR) разница между константой и переменной в приведенных выше примерах легко теряется за счет постоянного распространения.
Цель предложения состоит в том, чтобы позволить авторам компилятора применять желательные преобразования, как показано ниже. Рассмотрим не столь необычный цикл:
int f(unsigned int n, int *a)
{ unsigned int i;
int s;
s = 0;
for (i = 10U; i <= n; i++)
{
s += a[i];
}
return s;
}
По соображениям архитектуры (например, аппаратные петли) мы хотели бы преобразовать этот код в:
int f(unsigned int n, int *a)
{ unsigned int i;
int s;
s = 0;
for (i = 0; i < n-9; i++)
{
s += a[i+10];
}
return s;
}
Без предложения 6.8.5 ad 6 это невозможно, потому что если n
равно UINT_MAX
, цикл может не завершиться. Тем не менее человеку совершенно ясно, что это не намерение автора этого кода. Пункт 6.8.5 ad 6 теперь позволяет это преобразование. Однако способ, которым это достигается, не очень практичен для писателя компилятора, поскольку синтаксическое требование бесконечного цикла трудно поддерживать на IR.
Обратите внимание, что важно, чтобы n
и i
были unsigned
, поскольку переполнение на signed int
дает поведение undefined, и поэтому преобразование может быть оправдано по этой причине. Однако эффективный код выигрывает от использования unsigned
, за исключением большего положительного диапазона.
Альтернативный подход
Наш подход заключался бы в том, что автор кода должен выразить свое намерение, например, вставляя assert(n < UINT_MAX)
перед циклом или некоторой гарантией Frama-C. Таким образом, компилятор может "доказать" окончание и не должен полагаться на пункт 6.8.5 ad 6.
P.S: Я смотрю проект от 12 апреля 2011 года, поскольку paxdiablo явно смотрит на другую версию, может быть, его версия новее. В его цитате элемент постоянного выражения не упоминается.
Ответ 2
После проверки проекта стандарта C99, я бы сказал "нет", это не undefined. Я не могу найти какой-либо язык в проекте, который упоминает требование о завершении итераций.
Полный текст параграфа, описывающего семантику итерирующих утверждений:
Оператор итерации вызывает оператор, называемый телом цикла для выполнения несколько раз, пока контрольное выражение не сравнится с 0.
Я бы ожидал, что там появятся какие-либо ограничения, такие как тот, который указан для С++ 11, если это применимо. Существует также раздел "Ограничения", который также не упоминает о таком ограничении.
Конечно, реальный стандарт может сказать что-то еще, хотя я сомневаюсь.
Ответ 3
В C11 6.8.5 Iteration statements /6
появляется следующая инструкция:
Оператор итерации, управляющее выражение которого не является постоянным выражением, которое не выполняет операций ввода-вывода, не получает доступ к изменчивым объектов и не выполняет никаких операций синхронизации или атома в своем теле, управляя выражением, или (в случае оператора for) его выражение-3 может быть реализовано завершением реализации.
Так как while(1);
использует константное выражение, реализация не может допускать, что он завершится.
Компилятор может свободно удалить такой цикл полностью, поскольку выражение не является постоянным, и все остальные условия аналогичным образом выполняются, даже если он не может быть окончательно доказан, что цикл завершится.
Ответ 4
Самый простой ответ включает цитату из §5.1.2.3p6, в которой указаны минимальные требования соответствующей реализации:
Наименьшие требования к соответствующей реализации:
- Доступ к изменчивым объектам оценивается строго в соответствии с правила абстрактной машины.
- При завершении программы все данные, записанные в файлы, должны быть идентичный результату, что выполнение программы в соответствии с абстрактная семантика.
- Динамика ввода и вывода интерактивных устройств место, указанное в 7.21.3. Цель этих требований состоит в том, что небуферизованный или линейно-буферизованный выход появляются как можно скорее, чтобы убедитесь, что запрос сообщений действительно появляется до программы ожидая ввода.
Это наблюдаемое поведение программы.
Если машинный код не может выполнить наблюдаемое поведение из-за выполненных оптимизаций, компилятор не является компилятором C. Каково наблюдаемое поведение программы, которая содержит только такой бесконечный цикл, в момент прекращения? Единственный способ, которым могла бы закончиться такая петля, - это сигнал, вызывающий ее преждевременное завершение. В случае SIGTERM
программа завершается. Это не приведет к наблюдаемому поведению. Следовательно, единственная действительная оптимизация этой программы - это компилятор, предохраняющий систему, закрывающую программу и генерирующую программу, которая заканчивается немедленно.
/* unoptimised version */
int main() {
for (;;);
puts("The loop has ended");
}
/* optimised version */
int main() { }
Одна из возможностей заключается в том, что сигнал поднимается и вызывается longjmp, чтобы заставить выполнение перейти в другое место. Кажется, что единственное место, где можно было бы спрыгнуть, было где-то достигнуто во время выполнения до цикла, поэтому предоставление компилятора достаточно интеллектуально, чтобы заметить, что сигнал повышен, что приводит к тому, что выполнение переходит в другое место, оно может потенциально оптимизировать цикл (и поднятие сигнала) в пользу немедленного прыжка.
Когда несколько потоков входят в уравнение, допустимая реализация может передать право собственности на программу из основного потока на другой поток и завершить основной поток. Наблюдаемое поведение программы должно быть наблюдаемым, независимо от оптимизаций.