Поведение пустого цикла for с точки зрения стандарта
Какое поведение ожидать от кода с точки зрения стандарта?
int main(){for(;;){}}
- Ошибка компиляции (CE)
- Ошибка времени выполнения (RE)
- Неопределённое поведение (UB)
- Неспецифицированное поведение (unspecified)
- Зависит от реализации (implementation defined)
- Корректная программа (well-formed)
Ответы (2 шт):
Вопрос неточно сформулирован.
Если
int main(){for(;;){}}
Это вся программа, то к ней нет претензий.
Однако, если это многопоточная программа, т.е. в программе есть ещё статические конструкторы порождающие потоки, то нарушаются требования C++20 п. 6.9.2.2 Forward progress. Таким образом получаем: неопределенное поведение (undefined behavior).
Яндекс в помощь: https://en.cppreference.com/w/cpp/language/memory_model#Forward_progress
P.S.
Как заметил @HolyBlackCat (уже в чате), clang 13.0.0 с ключом -O порождает в этом случае безусловно ошибочный код.
Причём ошибочный код порождается даже для программ, которые гарантированно завершаются (т.е. не нарушающих требования п. 6.9.2.2), скажем:
#include <cstdlib>
int main()
{
system("(sleep 1; kill $PPID)&");
for(;;)
;
return 0;
}
или
#include <unistd.h>
int main()
{
alarm(1);
for(;;)
;
return 0;
}
Возможно это ошибка компилятора clang-13, возможно это ошибка в стандарте C++20, но clang-13 не соответствует С++20 и текущим черновикам C++2x.
Т.е. возможно в стандарте C++2х следует исправить:
- В п. 6.9.2.3 строка 1.1 следует написать не просто "terminate", а без учёта сигналов, и точно указать, что это UB;
- В приложении C указать, что теряется совместимость однопоточных приложений использующих сигналы.
Реализация может быть уверена, что любой поток в конечном итоге выполнит одно из следующих действий:
- прервётся,
- вызовет библиотечную функцию ввода-вывода,
- выполнит доступ к некоторому volatile glvalue,
- выполнит операцию синхронизации, либо атомарную операцию.
The implementation may assume that any thread will eventually do one of the following:
- terminate,
- make a call to a library I/O function,
- perform an access through a volatile glvalue, or
- perform a synchronization operation or an atomic operation.
[Note 1: This is intended to allow compiler transformations such as removal of empty loops, even when termination cannot be proven. — end note]
Приведённая программа не делает ничего из написанного выше, т.е. нарушает требования стандарта языка, следовательно, её поведение не определено.
Это раздел "Multi-threaded executions and data races", но где здесь многопоточность?
В приведённой цитате ничего не говорится о количестве потоков. Любой поток должен удовлетворять приведённым ограничениям, включая главный поток программы, в рамках которого выполняется функция main. basic.start.main / 1:
[...] Executing a program starts a main thread of execution in which the
mainfunction is invoked. [...]
в C++20/23 не написано прямо и недвусмысленно, что это UB
Для того, чтобы поведение некоторого кода было неопределённым не требуется явным образом использовать слова «undefined behavior». Поведение программы не определено также в том случае, если стандарт языка явным образом не специфицирует наблюдаемое поведение. defns.undefined / Note 1:
Undefined behavior may be expected when this document omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data.
Также см. стандарт языка C, n1570, 4 / 2:
If a ‘‘shall’’ or ‘‘shall not’’ requirement that appears outside of a constraint or runtimeconstraint is violated, the behavior is undefined. Undefined behavior is otherwise indicated in this International Standard by the words ‘‘undefined behavior’’ or by the omission of any explicit definition of behavior. There is no difference in emphasis among these three; they all describe ‘‘behavior that is undefined’’.
Приведённая в начале ответа цитата не описывает явным образом, что произойдёт при нарушении указанных ограничений, следовательно их нарушение вызывает неопределённое поведение.
ОК, подойдём к вопросу с другой стороны...
Посмотрим документ N1528: Why undefined behavior for infinite loops? от 2010 года в котором один из участников групп WG14 и WG21 по стандартизации языков C и C++ обсуждает формулировку пункта 6.8.5p6 черновика стандарта языка C. На тот момент она выглядела так ( N1509: Optimizing away infinite loops):
6 An iteration statement that performs no input/output operations, does not access volatile objects, and performs no synchronization or atomic operations in its body, controlling expression, or (in the case of a for statement) its expression-3, may be assumed by the implementation to terminate.154)
- This is intended to allow compiler transformations such as removal of empty loops even when termination cannot be proven.
В приведённой формулировке также не используются слова «undefined behavior», однако, смысл написанного поясняется вполне прямо и не двусмысленно:
As N1509 correctly points out, the current draft essentially gives undefined behavior to infinite loops in 6.8.5p6.
но с позиций здравого смысла это д.б. бескончный цикл
В такой простой программе
int main(){for(;;){}}
мне не удалось добиться иного поведения кроме как бесконечный цикл. Поэтому рассмотрим более сложный пример, придуманный John Regehr в статье C Compilers Disprove Fermat’s Last Theorem. (Я немного подправил код из статьи, но суть не изменилась):
#include <iostream>
using std::cout;
using std::endl;
bool fermat ()
{
const long long MAX = 1'000'000;
long long a=1, b=1, c=1;
for (;;) {
if (a*a*a == b*b*b+c*c*c)
return true;
a++;
if (a>MAX) {
a=1;
b++;
}
if (b>MAX) {
b=1;
c++;
}
if (c>MAX) {
c=1;
}
}
return false;
}
int main() {
if (fermat()) {
cout << "Fermat's Last Theorem has been disproved.\n";
} else {
cout << "Fermat's Last Theorem has not been disproved.\n";
}
}
Данная программа пытается найти контрпример к Великой теореме Ферма полным перебором. Она перебирает все возможные тройки чисел {a, b, c}, где a, b и c принимают целочисленные значения из отрезка [1; 1'000'000].
Функция fermat содержит бесконечный цикл (for (;;)) из которого можно выйти только в том случае, если программа сможет найти пример, опровергающий Великую теорему Ферма, что невозможно.
Так как функция fermat никогда не прервёт выполнение и не осуществляет никакого другого наблюдаемого поведения, то поведение приведённой программы не определено.
Результат выполнения примера может быть следующим (clang, g++):
Fermat's Last Theorem has been disproved.
Компилятор смог понять, что цикл на самом деле бесконечный и просто выбросил его (причём в однопоточной программе). Более того фактически программа ведёт себя так, словно смогла опровергнуть Великую теорему Ферма, что довольно контринтуитивно.
Связанные вопросы на enSO: