Это ошибка Clang
... при встраивании функции, содержащей бесконечный l oop. Поведение отличается, когда while(1);
появляется непосредственно в основном, что пахнет очень глючно для меня.
См. @ ответ Арнавиона для сводки и ссылок. Остальная часть этого ответа была написана до того, как у меня было подтверждение, что это ошибка, не говоря уже об известной ошибке.
Чтобы ответить на заглавный вопрос: Как сделать бесконечный пустой l oop что не будет оптимизировано? ? - сделать die()
макросом, а не функцией , чтобы обойти эту ошибку в Clang 3.9 и более поздних версиях. (Более ранние версии Clang либо сохраняют l oop, либо испускают call
для не встроенной версии функции с бесконечным l oop.) Это кажется безопасным, даже если print;while(1);print;
функция встроена в ее вызывающую ( Godbolt ). -std=gnu11
против -std=gnu99
ничего не меняет.
Если вы заботитесь только о GNU C, P__J __'s __asm__("");
внутри l oop также работает, и не должно мешать оптимизации любого окружающего кода для любых компиляторов, которые его понимают. GNU C Basi c asm операторы неявно volatile
, так что это считается видимым побочным эффектом, который должен «исполняться» столько раз, сколько это было бы в абстрактной машине C , (И да, Clang реализует GNU-диалект C, как описано в руководстве G CC.)
Некоторые люди утверждают, что было бы законно оптимизировать пустую бесконечность. oop. Я не согласен 1 , но даже если мы примем это, не может также быть законным для Кланга принимать заявления после того, как l oop недоступен, и пусть выполнение падает из конца функции в следующую функцию или в мусор, который декодируется как случайные инструкции.
(Это будет соответствовать стандартам для Clang ++ (но все же не очень полезно); бесконечные циклы без каких-либо побочных эффектов - это UB в C ++, но не C. Есть время (1); неопределенное поведение в C? UB позволяет компилятору в основном генерировать что-либо для кода на пути выполнения, который обязательно встретит UB. Оператор asm
в l oop позволит избежать этого UB для C ++. Но на практике компиляция Clang как C ++ не удаляет бесконечные пустые циклы с постоянным выражением, кроме как при встраивании, так же, как при компиляции как C.)
Внесение вручную while(1);
изменения как Clang компилирует это: бесконечно l oop присутствует в asm. Это то, что мы ожидаем от POV. *, Clang 9.0 -O3 компилируется как C (-xc
) для x86-64:
main: # @main
push rax # re-align the stack by 16
mov edi, offset .Lstr # non-PIE executable can use 32-bit absolute addresses
call puts
.LBB3_1: # =>This Inner Loop Header: Depth=1
jmp .LBB3_1 # infinite loop
.section .rodata
...
.Lstr:
.asciz "begin"
Тот же компилятор с теми же параметрами компилирует main
, который вызывает infloop() { while(1); }
к тому же сначала puts
, но затем просто прекращает выдавать инструкции для main
после этого момента. Как я уже сказал, выполнение просто падает из конца функции в любую функцию, следующую за ней (но со стеком, смещенным для входа в функцию, так что это даже не допустимый вызов вызова).
Допустимые параметры:
- испускает
label: jmp label
бесконечный l oop - или (если мы допустим, что бесконечный l oop можно удалить) испускает другой вызов для печати 2-й строки, и затем
return 0
из main
.
Сбой или иное продолжение без печати «недостижимый» явно не подходит для реализации C11, если нет UB, который я не заметил.
Сноска 1:
Для записи я согласен с @ ответом Лундина, который цитирует стандарт для доказательства того, что C11 не допускает предположения завершения для бесконечных циклов с постоянным выражением, даже когда они пусты (нет ввода-вывода, энергозависимости, синхронизации или других видимых побочных эффектов).
Это набор условий, которые пусть компиляция al oop к пустому асму l oop для обычного процессора. (Даже если тело не было пустым в источнике, назначения переменных не могут быть видны другим потокам или обработчикам сигналов без гонки данных UB, пока работает l oop. Поэтому соответствующая реализация может удалить такой l oop тел, если он этого хотел. Тогда остается вопрос, можно ли удалить сам l oop. ИСО С11 явно говорит нет.)
Учитывая, что С11 выделяет этот случай как тот, где реализация не могу предположить, что l oop завершается (и что это не UB), кажется ясным, что они предполагают присутствие l oop во время выполнения. Реализация, нацеленная на процессоры с моделью исполнения, которая не может выполнять бесконечное количество работы за конечное время, не имеет оснований для удаления пустой константы бесконечной l oop. Или даже в целом, точная формулировка о том, можно ли «предположительно прекратить» или нет. Если al oop не может завершиться, это означает, что более поздний код недоступен, независимо от того, какие аргументы вы приводите о математике и бесконечности и сколько времени занимает выполнение бесконечного объема работы на некоторой гипотетической машине .
Кроме того, Clang - это не просто DeathStation 9000, совместимый с ISO C, он предназначен для практического программирования низкоуровневых систем, включая ядра и встроенные компоненты. Итак, независимо от того, принимаете ли вы аргументы о том, что C11 разрешает удаление while(1);
, не имеет смысла, что Clang действительно захочет это сделать. Если вы напишите while(1);
, это, вероятно, не было случайностью. Удаление циклов, которые заканчиваются бесконечно случайно (с управляющими выражениями переменных времени выполнения), может быть полезным, и для компиляторов имеет смысл сделать это.
Редко, когда вы хотите просто вращаться до следующего прерывания, но если вы напишите это в C, это определенно то, что вы ожидаете. (И что происходит в G CC и Clang, за исключением Clang, когда бесконечный l oop находится внутри функции-оболочки).
Например, в примитивном ядре ОС если у планировщика нет задач для запуска, он может запустить простоя задачу. Первой реализацией этого может быть while(1);
.
Или для оборудования без какой-либо функции энергосбережения в режиме ожидания, которая может быть единственной реализацией. (До начала 2000-х это было, я думаю, не редко на x86. Хотя инструкция hlt
существовала, IDK, если она экономила значимое количество энергии до тех пор, пока центральные процессоры не начали иметь состояния простоя с низким энергопотреблением.)