volatile / restrict / static
Тема дорожной карты · Embedded / Системный программист
Тройка квалификаторов C, без которых embedded-код либо не компилируется, либо работает некорректно. volatile сообщает компилятору «не оптимизируй обращения к этой переменной» — критично для регистров и переменных-флагов из прерываний. restrict (C99) обещает «эта pointer-переменная единственная ссылка на свой объект» — позволяет компилятору агрессивнее оптимизировать. static ограничивает область видимости и время жизни — фундаментальный инструмент модульности.
Как это работает
volatile: каждое чтение/запись переменной с этим квалификатором выполняется буквально — компилятор не кэширует значение в регистре. volatile uint32_t * const REG = (uint32_t*)0x40021000; обязателен для всех memory-mapped регистров и для переменных, изменяемых из ISR. Без него цикл while (flag); крутится бесконечно, потому что компилятор закешировал flag в регистре. restrict: используется в параметрах функций (void memcpy(void * restrict dst, const void * restrict src, size_t n)) — гарантия не-aliasing. Компилятор может развернуть цикл и использовать векторные инструкции. static: на файловом уровне — функция/переменная не видна извне (как private), внутри функции — переменная живёт между вызовами (но не shared между потоками без synchronization).
Когда применять
volatile — обязателен для каждого регистра периферии (CMSIS-заголовки уже всё пометили) и для глобальных переменных, которые ISR изменяет. restrict — для собственных DSP-функций обработки массивов, где компилятор сможет векторизовать. static — для всех внутренних функций модуля (не торчат в global namespace), для constants в .c-файлах, для буферов фиксированного размера внутри функций (без переаллокации в heap).
Типичные ошибки
Использование volatile для thread-safety — он НЕ заменяет atomic-операции и memory barriers; для shared-данных нужны _Atomic или explicit barriers. Применение volatile к структурам, где надо защитить только одно поле — все поля становятся volatile, оптимизатор страдает. restrict на pointer-ах, которые на самом деле aliased — undefined behavior, выявить трудно. Перебор со static внутри функций приводит к non-reentrant коду — нельзя вызывать из прерывания и из main одновременно.