22

Может оптимизирующий компилятор удалить бесконечные циклы, не изменяет какие-либо данные, какМогут ли компиляторы исключать бесконечные петли?

while(1) 
    /* noop */; 

Из анализа графика компилятора потока данных можно вывести, что такой цикл «мертвый код» без каких-либо побочных эффектов.

Удаляет ли бесконечные петли, запрещенные стандартами C90/C99?

Разрешает ли C90 или C99 компилятор удалять такие циклы?

Обновление: «Версия Microsoft C 6.0 действительно сделала эту оптимизацию», см. Ссылку в кафе.

label: goto label; 
return 0; 

будет преобразован в

return 0; 
+2

Ожидается ожидание, и демоны не будут долго ждать. Я называю такую ​​конструкцию «мертвой» в средствах потока данных. Если оператор не изменяет никаких переменных и не содержит побочных эффектов, его можно устранить, оптимизируя компилятор. – osgx

+0

Код после цикла не «недоступен», цикл может быть прерван сигналом, и в обработчике сигнала может быть «longjmp». – osgx

+3

«Оптимизация бесконечных циклов» == «Как сделать бесконечные петли закончены быстрее» –

ответ

19

C11 уточняет ответ на этот вопрос, в проекте C11 стандарта раздел 6.8.5итерационных заявления добавил следующий пункт:

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

и сноска 157 говорит:

Это предназначено, чтобы компилятор преобразования, такие как удаление пустых петель, даже если прекращение не может быть доказано.

Так что ваш конкретный пример:

while(1) 
    /* noop */; 

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

Бесконечные циклы, как UB

Так почему составители позволили оптимизировать на бесконечные циклы, за исключением приведенной выше, а Ханс Бем дает обоснование для создания бесконечных циклов неопределенное поведение в: N1528: Why undefined behavior for infinite loops?, следующая цитата дает хорошее чувство для выпуска вовлеченного:

Как N1509 правильно указывает, текущий проект по существу дает неопределенное поведение на бесконечные циклы в 6.8.5p6. Основная проблема для заключается в том, что он позволяет коду перемещаться по потенциальному циклу без конца. Например, предположим, что мы имеем следующие петли, , где граф и count2 являются глобальные переменные (или имели их адрес принято), а р является локальной переменной, адрес которого не был принят:

for (p = q; p != 0; p = p -> next) { 
    ++count; 
} 
for (p = q; p != 0; p = p -> next) { 
    ++count2; 
} 

Может эти два цикла объединяются и заменяются следующим циклом?

for (p = q; p != 0; p = p -> next) { 
     ++count; 
     ++count2; 
} 

Без специального разрешения в 6.8.5p6 для бесконечных циклов, эта будет запрещен: если первый цикл не завершается, поскольку Q указует на круговой список, оригинал никогда не пишет count2. Таким образом, можно запускать параллельно с другим потоком, который обращается или обновлений count2. Это уже не безопасно с преобразованной версией , которая делает доступ count2 несмотря на бесконечный цикл. Таким образом, трансформация потенциально представляет собой гонку данных.

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

C99

Поскольку C99 не имеет этого выкроить, мы будем смотреть на как если бы правило покрыты в разделе 5.1.2.3, который в основном говорит о том, что компилятор имеет только эмулировать наблюдаемое поведение программы, требования заключаются в следующем:

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

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

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

while(1) ; 
printf("hello world\n") ; 

Многие утверждают, что осуществление прекращения процесса также наблюдаемое поведение, это положение берется в C Compilers Disprove Fermat’s Last Theorem:

компилятор дается большая свобода в том, как он реализует программу C, но его выход должен иметь тот же внешне видимое поведение, что программа будет иметь при интерпретированы «с абстрактной машины», который описан в стандарте , Многие знающие люди (включая меня) читают это, говоря, что поведение завершения программы не должно изменяться. Очевидно, что некоторые компиляторы не согласны или не считают, что это имеет значение. Тот факт, что разумные люди не согласны с интерпретацией, как представляется, указывает на то, что стандарт C является ошибочным.

Update

я как-то пропустил последующую деятельность в вышеуказанной статье, Compilers and Termination Revisited в котором говорится следующее о разделе 5.1.2.3:

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

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

Это относится также к бесконечным петлям goto?

Я считаю, что цель состоит в том, что это также относится и к бесконечным петлям goto. В C++ это явно случай, так как раздел 1.10[intro.multithread] говорит:

Реализация может предположить, что любой поток, в конечном счете сделать один из следующих

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

, а затем намерение, выраженные в N1528 является то, что С и С ++ стандарт согласны:

Так как компилятор бэкенды, как правило, разделены между составителями C и C++, представляется наиболее важным, что WG14 и РГ21 согласны с тем, какое решение принято. Альтернативами было бы специальное обращение с двумя языками по внутреннему интерфейсу или отключение оптимизации при обработке кода C. Ничего не представляется желательным.

и в конце говорит:

WG21 рассматривает улучшенную формулировку, что делает лечение последовательным. Надеемся, что WG14 проведет все изменения.

В настоящее время стандарт C11 не содержит аналогичную формулировку в разделе 5.1.2.4многопоточных казни и гонки данных но учитывая N1528, кажется разумным предположить, компиляторы будут относиться к бесконечным Гото петлями, как неопределенное поведение в C и C++.

Примечание также см. US comment 38 here и N3196, который является бумагой, с которой это изменение было применено.

+1

Кажется, что в формулировке C11 подразумевается, что 'while (1,1)/* no-op * /;' * can * можно оптимизировать, так как введение оператора запятой означает, что оно больше не является постоянным выражением. – caf

+0

@caf Я согласен * Константные выражения не должны содержать назначение, приращение, декремент, вызов функции, или операторы запятой * –

+0

Как насчет 'goto'? –

3

Они являются необходимостью при написании демонов. Почему ты хочешь назвать их мертвым кодом?

+5

Это был бы не очень хороший демон с тех пор, пока (1); технически не помещает эту нить в режим сна и как очень интенсивный процессор. Лучше было бы что-то вроде while (1) Sleep (5000); или что-то подобное – Toad

+0

Это побочный вопрос. Во всяком случае, 'while (1)' ничего, кроме _dead code_ - это моя точка. И да, я думаю, что я видел, как какой-то инструмент анализа кода сообщает об этом как о бесконечном цикле. – dirkgently

+2

Но 'while (1);' * is * по существу мертвый код - он ничего не полезен, кроме блока CPU. Гораздо лучше иметь что-то вроде: char exiting = 0; while (! Exiting) { exiting = process_event(); } – TMN

8

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

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

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

9

Невозможно обнаружить бесконечные циклы повсеместно: см. the Halting Problem. Таким образом, лучший любой компилятор мог бы сделать, это принять достойное предположение - например, очевидный случай, упомянутый в OP.

Но почему это было бы желательно? Я мог видеть выдачу предупреждения и по-прежнему позволяя поведение, но удалить цикл не является «оптимизацией» - он изменяет поведение программы!

+2

Первое правило проблемы с остановкой заключается в том, что никто не упоминает проблему остановки. –

+7

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

4

Это было обсуждено много раз до comp.lang.c (например, here) без, насколько мне известно, консенсусного результата.

+0

Спасибо! Можете ли вы найти еще несколько ссылок на такие потоки в usenet? – osgx

+8

Я уверен, что вы можете использовать Google так же хорошо, как я. – caf

0

Я считаю, что новые стандарты явно предусматривают, что если кусок кода не имеет доступа к каким-либо изменчивым переменным, выполнять операции ввода-вывода и т. Д., Любой другой код, который не полагается на что-либо, вычисленное в первой части, может быть произвольно упорядочен до Это. Если бесконечный цикл не выполняет никаких операций ввода-вывода и не вычисляет какое-либо значение, которое используется позже в программе, компилятор может просто отложить выполнение цикла до завершения всего остального.

+0

Вы можете верить даже в Бога, но не может быть стандарта ИСО по поведению Бога. Существуют ли подобные C-подобные языки? – osgx

+1

@osgx: рассмотрите код «int main (void) {do_something(); do_something_else(); return 0;}« Предположим, что компилятор может посмотреть на код и определить, что do_something() не пишет какую-либо изменчивую переменную, он пишет любую переменную, которую do_something_else() когда-либо будет использовать. Единственное, что могло бы сделать do_something(), которое могло бы изменить выход do_something_else(), было бы бесконечным циклом. Если компилятору разрешалось только отбрасывать код, который не мог бы циклически перемещаться, он был бы вынужден включать много бесполезного кода. Предотвращение отбрасывания бесконечных циклов упрощает работу. – supercat

+0

c99 5.1.2.3 «Доступ к изменчивому объекту, изменение объекта, изменение файла или вызов функции , выполняющей любую из этих операций» - это единственные побочные эффекты. Они «меняют состояние среды исполнения». «Фактическая реализация не должна оценивать часть выражения, если она может вывести, что ее значение не используется и что никаких необходимых побочных эффектов не производится» – osgx

Смежные вопросы