Почему программа работает с -O0 и ломается с -O2?
Обновлено Редакция GAW
Оптимизатор не меняет смысл правильной программы на C. Стандарт (раздел 5.1.2.3 C11) описывает поведение через абстрактную машину и разрешает не вычислять то, что не влияет на наблюдаемый результат. Наблюдаемы только обращения к volatile-объектам, ввод-вывод и запись в файлы. Всё, что программа «имела в виду», но не выразила так, исчезает при -O2, а при -O0 случайно работало.
Три частые причины
| Код | С -O0 | С -O2 | Исправление |
|---|---|---|---|
while (!flag); где flag меняет прерывание | читает flag каждый раз | читает один раз — вечный цикл | volatile у flag |
for (i = 0; i < 100000; i++); как задержка | крутится | цикл без эффекта удалён | таймер или _delay_ms |
| чтение регистра через обычный указатель | читает каждый раз | чтение объединено или выброшено | указатель на volatile |
Первый случай avr-libc разбирает в своём FAQ как самый частый вопрос про прерывания; почему компилятор так поступает — в статье о volatile. Пустой цикл задержки ничего наблюдаемого не делает, и по правилу «как если бы» (5.1.2.3) компилятор вправе удалить его целиком, поэтому для задержек нужен таймер или функция из библиотеки — их возможности описаны в калькуляторе задержек AVR.
Неопределённое поведение
Хуже всего ошибки, о которых компилятор не предупреждает: переполнение знакового целого, чтение за границей массива, обращение к одному объекту через указатели разных типов. С -O0 такой код обычно делает «очевидное», а оптимизатор вправе считать, что такого не бывает, и строить код на этом допущении. Как писать на C для микроконтроллеров без этих ловушек, рассказывает статья «Язык C для микроконтроллеров».
Если переменную делят основная программа и обработчик, одного volatile мало: многобайтовое значение нужно читать атомарно. Об этом — статья «Прерывания и общие данные».
Похожие вопросы
Почему отладчик показывает, что переменная «optimized out»?
При оптимизации переменная может жить только в регистре или быть вычислена заранее, и в памяти её нет. Это не ошибка. Для отладки собирают с -Og или временно объявляют переменную volatile, но рабочую сборку проверяют с той же оптимизацией, что пойдёт в изделие.
Не проще ли выпускать прошивку с -O0?
Ошибка от этого не исчезнет, а только спрячется до следующего компилятора или изменения кода. К тому же -O0 даёт заметно больший и медленный код, а функции задержки avr-libc без оптимизации вообще не обеспечивают заданное время.
Как искать такую ошибку?
Включите предупреждения -Wall -Wextra и проверьте все переменные, которые меняют обработчики прерываний или DMA, — у них должен быть volatile. Затем ищите пустые циклы задержки, приведения указателей между типами и арифметику со знаковыми переполнениями — типичные случаи неопределённого поведения.
Источники
- ISO/IEC 9899:201x (C11), Committee Draft N1570, April 2011 — 5.1.2.3p4 — реализация может не вычислять часть выражения, если его значение не используется и нет нужных побочных эффектов; обращения к volatile-объектам — побочные эффекты, их выполняют строго по абстрактной машине
- GCC 16.2 Manual: When is a Volatile Object Accessed?; Options That Control Optimization — обращения к volatile-объектам сохраняются и упорядочиваются относительно друг друга; обращения к обычным объектам компилятор может переставлять и объединять; -Og — оптимизация с сохранением удобства отладки
- AVR-LibC 2.3.2: doc/api/faq.dox.in — «Why doesn't my program recognize a variable updated in an interrupt routine?» — с оптимизацией флаг без volatile читается один раз, и цикл while (flag == 0) никогда не завершается; «Which -O flag to use?» — больший номер -O не означает лучшую оптимизацию; include/util/delay.h — функции задержки работают как задумано только с включённой оптимизацией, без неё выдаётся предупреждение «Compiler optimizations disabled»
Нашли ошибку?
Смотрите также
- volatileКвалификатор C для регистров и переменных прерываний
- Прерывания и общие данныеISR на AVR и Cortex-M, атомарность, критические секции, кольцевой буфер
- C для микроконтроллеровТипы, stdint, volatile, атомарность, регистры, константы во Flash
- Задержки AVR: _delay_msТакты и фактическое время _delay_ms, _delay_us, циклов; пределы и округление