Ответ 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 явно смотрит на другую версию, может быть, его версия новее. В его цитате элемент постоянного выражения не упоминается.