Ошибка компиляции C ++?

У меня есть следующий код:

#include  #include  using namespace std; int main() { complex delta; complex mc[4] = {0}; for(int di = 0; di < 4; di++, delta = mc[di]) { cout << di << endl; } return 0; } 

Я ожидаю, что он выдает «0, 1, 2, 3» и останавливается, но выводит бесконечную серию «0, 1, 2, 3, 4, 5, …..»,

Похоже, что сравнение di<4 не работает и всегда возвращает true.

Если я просто прокомментирую ,delta=mc[di] , я получаю «0, 1, 2, 3» как обычно. В чем проблема с невинным назначением?

Я использую Ideone.com g ++ C ++ 14 с опцией -O2.

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

  • Доступ к mc вне границ – это неопределенное поведение
  • Не допускайте неопределенного поведения
  • Поэтому di < 4 всегда истинно, так как иначе mc[di] вызывает неопределенное поведение

gcc с -fno-aggressive-loop-optimizations оптимизацией и с использованием -fno-aggressive-loop-optimizations приводит к исчезновению поведения бесконечного цикла ( см. его в реальном времени ). В то время как живой пример с оптимизацией, но без -fno-агрессивных циклов-оптимизаций демонстрирует поведение бесконечного цикла, которое вы наблюдаете.

Пример живого примера с символом godbolt показывает, что di < 4 проверка удалена и заменена и безусловным jmp:

 jmp .L6 

Это почти идентично случаю, описанному в GCC pre-4.8 Breaks Broken SPEC 2006 Benchmarks . Комментарии к этой статье превосходны и заслуживают внимания. Он отмечает, что clang поймал случай в статье, используя -fsanitize=undefined который я не могу воспроизвести для этого случая, но gcc, используя -fsanitize=undefined ( см. -fsanitize=undefined прямом эфире ). Вероятно, самая печально известная ошибка вокруг оптимизатора, делающая вывод о неопределенном поведении, - это удаление проверки нулевого указателя ядра Linux .

Хотя это агрессивная оптимизация, важно отметить, что, как утверждает стандарт C ++, неопределенное поведение:

поведение, для которого настоящий международный стандарт не предъявляет никаких требований

Что по существу означает, что все возможно, и оно отмечает ( внимание мое ):

[...] Допустимое неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемыми результатами , поведения во время перевода или выполнения программы в документально оформленной форме, характерной для среды (с выдачей диагностического сообщения или без нее), до прекращения перевода или (с выдачей диагностического сообщения). [...]

Чтобы получить предупреждение от gcc, нам нужно переместить cout вне цикла, а затем мы увидим следующее предупреждение ( см. Его в прямом эфире ):

 warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations] for(di=0; di<4;di++,delta=mc[di]){ } ^ 

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

Обратите внимание, что -fno-aggressive-loop-optimizations описаны в примечаниях к выпуску gcc 4.8 .

Поскольку вы увеличиваете di прежде чем использовать его для индексации mc , в четвертый раз через цикл вы будете ссылаться на mc [4], который находится за концом вашего массива, что, в свою очередь, может привести к неприятному поведению.

У вас есть следующее:

 for(int di=0; di<4; di++, delta=mc[di]) { cout< 

Попробуйте это вместо этого:

 for(int di=0; di<4; delta=mc[di++]) { cout< 

РЕДАКТИРОВАТЬ:

Чтобы уточнить, что происходит, разрешается прерывать итерацию вашего цикла:

1-я итерация: сначала di устанавливается в 0. Проверка сравнения: не менее 4? Да, хорошо. Приращение di на 1. Теперь di = 1. Возьмите «n-й» элемент mc [] и установите его delta. На этот раз мы захватываем второй элемент, так как это индексированное значение равно 1, а не 0. Наконец, выполните блок кода / с внутри цикла for.

2-я итерация: теперь di установлено в 1. Проверка сравнения: меньше ли 4? Да и продолжай. Приращение di на 1. Теперь di = 2. Возьмите «n-й» элемент mc [] и установите его delta. На этот раз мы захватываем третий элемент, так как это индексированное значение равно 2. Наконец, выполните блок кода / s внутри цикла for.

3-я итерация: теперь di установлено на 2. Проверка сравнения: менее 4? Да и продолжай. Приращение di на 1. Теперь di = 3. Возьмите «n-й» элемент mc [] и установите его delta. На этот раз мы захватываем 4-й элемент, так как это индексированное значение равно 3. Наконец, выполните блок кода / с внутри цикла for.

4-я итерация: теперь di задано 3. Проверка сравнения: не менее 4? Да и продолжай. Приращение di на 1. Теперь di = 4. (Вы можете видеть, где это происходит?) Возьмите элемент «nth» из mc [] и установите его как дельту. На этот раз мы захватываем пятый элемент, так как это индексированное значение равно 4. Uh У нас проблема; размер нашего массива - всего 4. В Delta теперь есть мусор, и это неопределенное поведение или коррупция. Наконец, выполните код / ​​сек внутри цикла for, используя «мусорный дельта».

5-я итерация. Теперь di установлено в 4. Проверка сравнения: не менее 4? Нет, выходите из цикла.

Коррупция за счет превышения границ смежной памяти (массива).

Это потому, что di ++ выполняется при последнем запуске цикла.

Например;

 int di = 0; for(; di < 4; di++); // after the loop di == 4 // (inside the loop we see 0,1,2,3) // (inside the for statement, after di++, we see 1,2,3,4) 

Вы обращаетесь к mc [], когда di == 4, поэтому это проблема за пределами границ, потенциально разрушая часть стека и искажая переменную di.

решение будет:

 for(int di = 0; di < 4; di++) { cout << di << endl; delta = mc[di]; } 
  • Является ли чтение неопределенного значения неопределенным поведением?
  • Итак, почему i = ++ i + 1 четко определен в C ++ 11?
  • Виртуальный деструктор и неопределенное поведение
  • Сопоставлено ли целочисленное переполнение по-прежнему неопределенным поведением в C ++?
  • Почему операция сдвига влево вызывает Undefined Behavior, когда левый операнд имеет отрицательное значение?
  • В какой момент разыменование нулевого указателя становится неопределенным поведением?
  • Неопределенное поведение и точки последовательности
  • Неинициализированная локальная переменная - самый быстрый генератор случайных чисел?
  • Рядом постоянное время вращается, что не нарушает стандарты
  • Почему char * вызывает неопределенное поведение, а char - нет?
  • Являются ли множественные мутации в списке инициализаторов неопределенным поведением?
  • Interesting Posts

    Серийные порты COM3-COM49 используются, но не указаны в диспетчере устройств

    Haskell: Что такое нормальная форма слабой головы?

    8086 на DOSBox: ошибка с инструкцией idiv?

    Включение зависимостей в фильтры действия ASP.NET MVC 3. Что не так с этим подходом?

    Как нарисовать заполненный треугольник в андроидном canvasе?

    Оператор обновления с внутренним соединением в Oracle

    Почему 0 <-0x80000000?

    Как определить, когда fragment становится видимым в ViewPager

    Если Int32 является просто псевдонимом для int, как class Int32 может использовать int?

    Получение Chunked HTTP-данных с помощью Winsock

    Двойная загрузка Windows и Ubuntu – * но * с Ubuntu, уже установленным на отдельный жесткий диск

    Какую информацию о вашем ПК можно получить на сайтах / в Интернете?

    Используйте трекбол для прокрутки, масштабирования и т. Д.

    Java: рекомендуемое решение для глубокого клонирования / копирования экземпляра

    Перенаправление на Ajax Jquery Call

    Давайте будем гением компьютера.