Является (1); неопределенное поведение в C?

19

В C ++ 11 это неопределенное поведение , но имеет ли место в C, что while(1); не определено Поведение?     

задан Tony The Lion 08.05.2013 в 10:44
источник
  • Я предполагаю, что если for (;;) оператор корректно определен в C, тогда while (1) не должен быть Undefined в C .... помните, что обнаружение бесконечного цикла является неразрешимой проблемой .. –  Grijesh Chauhan 08.05.2013 в 12:10
  • Если вам нравится, я мог бы немного подробнее описать 6.8.5 объявление 6 и, особенно, почему маловероятно, что компания-компилятор, над которой я работаю, будет использовать этот пункт. –  Bryan Olivier 08.05.2013 в 12:46
  • @BryanOlivier пойти на это :) –  Tony The Lion 08.05.2013 в 12:52
  • @ Тони, спасибо, всегда приятно кататься на хобби. –  Bryan Olivier 08.05.2013 в 13:27

4 ответа

22

Это четко определенное поведение. В C11 добавлен новый пункт 6.8.5 ad 6

  

Оператор итерации, контрольное выражение которого не является постоянным выражением, 156) , который не выполняет операций ввода-вывода, не получает доступ к неустойчивым объектам и не выполняет никаких операций синхронизации или атома в своем теле, выражение или (в случае оператора for) его выражение-3 может быть реализовано при завершении реализации. 157)

     

157) Предполагается разрешить преобразования компилятора, такие как удаление пустых циклов, даже если завершение невозможно доказать.

Поскольку управляющее выражение вашего цикла является константой, компилятор не может считать цикл завершенным. Это предназначено для реактивных программ, которые должны выполняться вечно, как операционная система.

Однако для следующего цикла поведение неясно

a = 1; while(a);

Фактически компилятор может или не может удалить этот цикл, в результате чего программа, которая может завершиться или не завершиться. Это не совсем неопределенно, так как не разрешено стирать жесткий диск, но это не следует избегать.

Однако есть еще одна загвоздка, рассмотрим следующий код:

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 дает неопределенное поведение, и поэтому преобразование может быть оправдано по этой причине. Однако эффективный код выигрывает от использования unsigned , кроме большего положительного диапазона.

Альтернативный подход

Наш подход будет заключаться в том, что автор кода должен выразить свое намерение, например, вставив assert(n < UINT_MAX) перед циклом или некоторой гарантией Frama-C. Таким образом, компилятор может «доказать» окончание и не должен полагаться на пункт 6.8.5 ad 6.

P.S: Я смотрю проект от 12 апреля 2011 года, поскольку paxdiablo явно смотрит на другую версию, может быть, его версия новее. В его цитате элемент постоянного выражения не упоминается.

    
ответ дан Bryan Olivier 08.05.2013 в 10:57
источник
  • Я тоже смотрю на n1570, и я заверяю вас, что цитата paxdiablo есть, в конце страницы с номером 150 (168 в номерах на странице Adobe Reader) ... –  mentally retarded 08.05.2013 в 13:00
  • @undefinedbehaviour Я только что скачал n1570, и у него все еще есть версия в моей цитате оговорке, где сделано исключение для «чье контрольное выражение не является постоянным выражением». Но, как я утверждаю выше, это действительно не помогает. –  Bryan Olivier 08.05.2013 в 13:35
  • Ах. Я не заметил этого дополнения. Очень хорошо. Тот, который вы смотрите, является самым современным стандартом C11. –  mentally retarded 08.05.2013 в 13:42
  • +1 очень хороший ответ –  Shafik Yaghmour 08.05.2013 в 13:51
  • Компилятор уже вынужден отслеживать, является ли распространенная константа постоянным выражением по другим причинам. Например, sizeof (* (char (*) [1]) a ++) не увеличивает а, а sizeof (* (char (*) [non_constexpr_1]) a ++). –  R.. 08.05.2013 в 16:12
Показать остальные комментарии
5

После проверки проекта стандарта C99 , я бы сказал «нет», это не определено. Я не могу найти какой-либо язык в проекте, который упоминает требование о завершении итераций.

Полный текст параграфа, описывающего семантику итерирующих утверждений:

  

Оператор итерации вызывает оператор, называемый телом цикла   для выполнения несколько раз, пока контрольное выражение не сравнится с 0.

Я бы ожидал, что там появятся какие-либо ограничения, такие как тот, который указан для C ++ 11, если это применимо. Существует также раздел «Ограничения», который также не упоминает о таком ограничении.

Конечно, реальный стандарт может сказать что-то еще, хотя я сомневаюсь.

    
ответ дан unwind 08.05.2013 в 10:51
источник
1

Самый простой ответ включает цитату из §5.1.2.3p6, в которой указаны минимальные требования соответствующей реализации:

  

Наименьшие требования к соответствующей реализации:

     

- Доступ к изменчивым объектам оценивается строго в соответствии с   правила абстрактной машины.

     

- При завершении программы все данные, записанные в файлы, должны быть   идентичный результату, что выполнение программы в соответствии с   абстрактная семантика.

     

- Динамика ввода и вывода интерактивных устройств   место, указанное в 7.21.3. Цель этих требований состоит в том, что   небуферизованный или линейно-буферизованный выход появляются как можно скорее, чтобы   убедитесь, что запрос сообщений действительно появляется до программы   ожидая ввода.

     

Это наблюдаемое поведение программы.

Если машинный код не может выполнить наблюдаемое поведение из-за выполненных оптимизаций, компилятор не является компилятором C. Каково наблюдаемое поведение программы, которая содержит только такой бесконечный цикл, в момент прекращения? Единственный способ, которым могла бы закончиться такая петля, - это сигнал, вызывающий ее преждевременное завершение. В случае SIGTERM программа завершается. Это не приведет к наблюдаемому поведению. Следовательно, единственная действительная оптимизация этой программы - это компилятор, предохраняющий систему, закрывающую программу и генерирующую программу, которая заканчивается немедленно.

/* unoptimised version */
int main() {
    for (;;);
    puts("The loop has ended");
}

/* optimised version */
int main() { }

Одна из возможностей заключается в том, что сигнал поднимается и вызывается longjmp, чтобы заставить выполнение перейти в другое место. Кажется, что единственное место, где можно было бы спрыгнуть, было где-то достигнуто во время выполнения до цикла, поэтому предоставление компилятора достаточно интеллектуально, чтобы заметить, что сигнал повышен, что приводит к тому, что выполнение переходит в другое место, оно может потенциально оптимизировать цикл (и поднятие сигнала) в пользу немедленного прыжка.

Когда несколько потоков входят в уравнение, допустимая реализация может передать право собственности на программу из основного потока на другой поток и завершить основной поток. Наблюдаемое поведение программы должно быть наблюдаемым, независимо от оптимизаций.

    
ответ дан mentally retarded 08.05.2013 в 12:32
источник
  • Ваше имя почти похоже на новинку на этот вопрос. –  Tony The Lion 08.05.2013 в 12:41
1

В C11 6.8.5 Iteration statements /6 появляется следующий оператор:

  

Оператор итерации, управляющее выражение которого не является константным выражением, которое не выполняет операций ввода-вывода, не получает доступ к изменчивым   объектов и не выполняет никаких операций синхронизации или атома в своем теле, управляя выражением, или (в случае оператора for) его выражение-3 может быть реализовано завершением реализации.

Так как while(1); использует константное выражение, реализация не допускает его завершения.

Компилятор is может удалить этот цикл полностью, это выражение не является постоянным, и все остальные условия аналогичным образом выполняются, даже если он не может быть окончательно доказан, что цикл завершится.     

ответ дан paxdiablo 08.05.2013 в 10:57
источник
  • Не совсем свободно предположить, что оно закончится. Необходимо будет провести дополнительную обработку, чтобы обеспечить соблюдение наблюдаемого поведения программы. Если код, следующий за циклом, не может быть достигнут, компилятор также должен будет его оптимизировать. –  mentally retarded 08.05.2013 в 13:09
  • @undefinedbehaviour Я прошу отличаться. Я думаю, что любое наблюдаемое поведение после цикла, которое может казаться недостижимым из-за цикла с переменной, маркером этого предложения может стать доступным и не нужно оптимизировать (сначала). –  Bryan Olivier 08.05.2013 в 13:44
  • @ R.I.P.Seb: Я хочу, чтобы стандарт указал, что компилятор разрешил делать на основе предположения. ИМХО, то, что может иметь смысл в качестве дефолта, было бы сказать, что «unsigned long long test (unsigned long long a) делает {a = outsideFunctionWith (a);} while (a! = 1); printf (« It terminated! » ); printf ("Результат =% lld", a); return a;} "будет вести себя так, как если бы" while "выполнялся параллельно с первым printf, но второй printf [и возврат из функции] придется ждать, пока «а» на самом деле не будет присвоено значение единицы. Если цель функции ... –  supercat 05.04.2016 в 18:19
  • ... заключается в том, чтобы подтвердить, что некоторая функция в конечном итоге вернется 1, и оптимизатор решит, что он «должен», и, следовательно, будет бесполезным. –  supercat 05.04.2016 в 18:20
  • @BryanOlivier Как я писал: «Там должна быть дополнительная обработка, чтобы обеспечить соблюдение наблюдаемого поведения программы». Можете ли вы сказать мне, почему эта программа ничего не печатает? –  mentally retarded 06.04.2016 в 02:52
Показать остальные комментарии