Разрешено ли компиляторам удалять бесконечные циклы, как это делает компилятор Intel C ++ с -O2?

Следующий код тестирования работает правильно в VS при отладке или выпуске, а также в GCC. Это также правильно для ICC с отладкой, но не при включенной оптимизации (-O2).

#include <cstdio>

class tClassA{
public:
  int m_first, m_last;

  tClassA() : m_first(0), m_last(0) {}
  ~tClassA() {}

  bool isEmpty() const {return (m_first == m_last);}
  void updateFirst() {m_first = m_first + 1;}
  void updateLast() {m_last = m_last + 1;}
  void doSomething() {printf("should not reach here\r\n");}
};

int main() {
  tClassA q;
  while(true) {
    while(q.isEmpty()) ;
    q.doSomething();
  }
  return 1;
}

Предполагается остановиться на while(q.isEmpty()). Однако, когда -O2 включен в ICC (выпуск), он начинает бесконечно «делать что-то».

Поскольку это однопоточная программа и isEmpty() следует оценивать как true, я не могу найти причин, по которым ICC должен вести себя таким образом? Я что-нибудь упускаю?


person Samuel    schedule 20.08.2010    source источник
comment
помогает, если m_first и m_last объявлены как 'volatile'? У меня нет доступа к ICC.   -  person Chubsdad    schedule 20.08.2010
comment
Еще одна дикая мысль: связано ли это с тем, что% s не указан в printf. printf (% s, сюда не должно доходить \ r \ n) ;?   -  person Chubsdad    schedule 20.08.2010
comment
По теме: Разрешено ли компиляторам исключать бесконечные циклы?   -  person James McNellis    schedule 20.08.2010
comment
Было бы полезно указать, какую версию ICC вы тестируете и на какой платформе.   -  person James McNellis    schedule 20.08.2010
comment
@Samuel: Хотел бы знать, для моей справки, помогло ли создание нестабильности участников. Это должно подавить любые эффекты флага оптимизации   -  person Chubsdad    schedule 20.08.2010
comment
1. Если m_first и m_last объявлены как изменчивые, проблем нет; если isEmpty () объявлен как изменчивый, проблем тоже нет. 2. Я использовал ICC 10 с VS   -  person Samuel    schedule 20.08.2010
comment
Что касается printf ..., это не имеет значения, вы можете делать что угодно в этой функции, например, {m_first ++; m_last ++; ...}   -  person Samuel    schedule 20.08.2010


Ответы (7)


Поскольку цикл while (q.isEmpty()) ; не содержит операторов, которые могут когда-либо вызвать видимый извне побочный эффект, весь цикл оптимизируется. По той же причине, что:

for (int i = 0; i < 10; i++)
    ;

могли быть оптимизированы, пока i не был volatile (сохранение в volatile объектов является частью «видимых извне» эффектов программы).

На языке C это действительно явное яблоко раздора относительно того, можно ли таким образом оптимизировать бесконечный цикл (я не знаю, как обстоят дела с C ++). Насколько мне известно, по этому поводу так и не удалось достичь консенсуса - умные и знающие люди встали на обе стороны.

person caf    schedule 20.08.2010
comment
Это могло быть причиной. Следующий код работает правильно: static int A = 0; while (q.isEmpty ()) {A = A + 1;} Но не следующее: static int A = 1; while (q.isEmpty ()) {A;} - person Samuel; 20.08.2010
comment
Интересный. Бесконечный цикл ничего не делает - хорошая возможность оптимизации (и понятно, почему кто-то может не согласиться). - person Suma; 20.08.2010
comment
Еще одна вещь: если while (true) используется для while (q.isEmpty ()), код работает правильно. - person Samuel; 20.08.2010
comment
Сума: Что касается стандарта, это правильное поведение, поскольку оно не имеет видимых побочных эффектов, оно не может изменить поведение программы, верно? (Неправильно - бесконечный цикл ДЕЙСТВИТЕЛЬНО изменяет программу, но, поскольку определение того, является ли цикл бесконечным или нет, в общем случае, по сути, является проблемой остановки, он принимает решение всегда удалять такие циклы. Если это нежелательно, тогда оно Вы, программист, должны сообщить компилятору, что он действительно имеет побочные эффекты, например, путем чтения или записи изменчивой переменной или некоторых специфичных для компилятора флагов, если они существуют) - person ; 28.01.2011
comment
@Samuel: Обратите внимание, что в вашем первом примере A в конечном итоге переполняется, вызывая неопределенное поведение. Поскольку оптимизатор может предположить, что UB не происходит, он может сделать вывод, что q.isEmpty() должно быть ложным в начале, и весь цикл может быть исключен. - person MSalters; 26.03.2013

Это действительно похоже на ошибку. Вот (довольно дикая) догадка о том, какие рассуждения могли к этому привести ...

После встраивания он видит:

while (q.m_first == q.m_last) /* do nothing */ ;
do_something();

и любая последовательность do nothing repeatedly ; do something может быть переведена просто как «что-то делать». Это падает, если повторяющаяся часть бесконечна (как в этом случае). Но, возможно, они не тестируют свою компиляцию на примерах, которые намеренно имеют бесконечный цикл ;-).

person Edmund    schedule 20.08.2010
comment
Обнаружение намеренного бесконечного цикла - это, по сути, проблема остановки, поэтому вместо этого они предполагают, что, поскольку он не имеет видимых побочных эффектов, он ничего не делает и, следовательно, бесполезен для программы и может быть удален. Очевидно, завершается он или нет, ЯВЛЯЕТСЯ видимым sde-эффектом для нас, но не для абстрактной машины C / C ++, определенной стандартом. Если вам нужен цикл, вы должны сообщить компилятору, что он действительно влияет на программу, например, делая одну из переменных чтения изменчивой. - person ; 28.01.2011

Есть ли шанс, что в фактическом коде, который вы создали и запустили, не было точки с запятой после while(q.isEmpty())? Это обязательно приведет к бесконечному вызову следующей строки.

person TheUndeadFish    schedule 20.08.2010
comment
1. есть точка с запятой 2. тот же результат с while (q.isEmpty ()) {} или while (q.isEmpty ()) {true;} - person Samuel; 20.08.2010
comment
Для кого угодно: действительно ли было необходимо голосование против? Я видел ряд случаев на SO, где опубликованный код не был точным используемым кодом и где реальная проблема была упущена или исправлена ​​в переводе. Я просто хотел убедиться, что это не результат простой, но легко упускаемой из виду ошибки. (В конце концов, разве мы все время от времени не тратили время на ошибку из-за ошибочной точки с запятой?) - person TheUndeadFish; 21.08.2010

Немного в стороне, эта версия icc делает то, что вы хотите. То есть никогда не вызывает doSomething().

[9:41am][wlynch@computer /tmp] icc --version
icc (ICC) 11.0 20081105
person Bill Lynch    schedule 20.08.2010

Стандарт C ++ позволяет удалять циклы без побочных эффектов, даже если они не завершаются:

Обычно считается, что важно разрешить преобразование потенциально незавершенных циклов (например, путем слияния двух циклов, которые повторяются по одному и тому же потенциально бесконечному набору, или путем исключения цикла без побочных эффектов), даже если это не может в противном случае будет оправдано в случае, когда первый цикл никогда не завершается. http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2429.htm

См. Обсуждение здесь: http://blog.regehr.org/archives/161

person Charles    schedule 21.08.2010

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

person linuxuser27    schedule 20.08.2010
comment
Вы не можете. Код asm оптимизирован, и вы ничего не видите. - person Samuel; 20.08.2010
comment
Я не совсем понимаю, что это значит. Вы хотите сказать, что вы разобрали полученный двоичный файл и цикл while () пропал? - person linuxuser27; 20.08.2010

Думаю, это могла быть ваша версия gcc. Я скомпилировал вашу прогу под 4.4.2 и она заработала ровно так, как должна.

person BT.    schedule 20.08.2010