Может ли компилятор ANSI C удалить цикл задержки?

Рассмотрим цикл while в ANSI C, единственной целью которого является отсрочка выполнения:

unsigned long counter = DELAY_COUNT;
while(counter--);

Я видел, что это часто использовалось для обеспечения задержек во встроенных системах, где, например. нет функции sleep, а таймеры или прерывания ограничены.

Мое чтение стандарта ANSI C состоит в том, что это может быть полностью устранено соответствующим компилятором. Он не имеет побочных эффектов, описанных в 5.1.2.3:

Доступ к изменчивому объекту, изменение объекта, изменение файла или вызов функции, выполняющей любые из этих операций, являются всеми побочными эффектами, которые являются изменениями состояния среды выполнения.

... и в этом разделе также говорится:

Фактическая реализация не должна оценивать часть выражения, если она может вывести, что ее значение не используется, и что не создаются необходимые побочные эффекты (в том числе любые вызванные вызовом функции или доступом к летучим объектам).

Означает ли это, что цикл можно оптимизировать? Даже если counter были volatile?

Примечания:

  • Что это не совсем то же самое, что Разрешено ли компиляторам исключать бесконечные циклы?, поскольку это относится к бесконечным циклам, и возникают вопросы о том, когда разрешена программа вообще прекратить. В этом случае программа, безусловно, продолжит эту линию в какой-то момент, оптимизирует или нет.
  • Я знаю, что делает GCC (удаляет цикл для -O1 или выше, если counter не является volatile), но я хочу знать, что диктует стандарт.

Ответ 1

Соответствие стандарту C соответствует правилу "как-если", с помощью которого компилятор может сгенерировать любой код, который ведет себя "как если бы", он выполнял ваши фактические инструкции на абстрактной машине. Так как не выполняющие какие-либо операции имеют одно и то же наблюдаемое поведение "как если бы" вы выполняли цикл, вполне допустимо не генерировать код для него.

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

Ситуация для переменных volatile отличается от ситуации, так как доступ к летучим считается как "наблюдаемый" эффект.

Ответ 2

Означает ли это, что цикл можно оптимизировать?

Да.

Даже если счетчик нестабилен?

Нет. Он будет читать и писать изменчивую переменную, которая имеет наблюдаемое поведение, поэтому она должна произойти.

Ответ 3

Если счетчик volatile, компилятор не может юридически оптимизировать цикл задержки. В противном случае он может.

Циклы задержки, как это плохо, потому что время их записи зависит от того, как компилятор создает для них код. Используя различные варианты оптимизации, вы можете добиться разных задержек, чего вряд ли стоит от цикла задержки.

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

Ответ 4

Стандарт определяет поведение, которое вы видите. Если вы создаете дерево зависимостей для DELAY_COUNT, вы увидите, что оно имеет свойство изменения без использования, что означает, что его можно устранить. Это относится к неустойчивому случаю. В неустойчивом случае компилятор не может использовать дерево зависимостей для попытки удалить эту переменную и, как таковая, остается задержка (поскольку волатильность означает, что аппаратное обеспечение может изменить отображаемое значение памяти ИЛИ в некоторых случаях означает "мне действительно это нужно, не бросайте его прочь" ). В случае, если вы смотрите на то, что он помечен как volatile, он сообщает компилятору, пожалуйста, не отбрасывайте его здесь по какой-то причине.