ESP-IDF, директивы #pragma Печать
Добавил(а) microsin   

При разработке встраиваемых устройств на чипах компании Espressif (ESP32, ESP32-C3 и других) директивы pragma (#pragma) являются очень полезными инструкциями для компилятора, которые передают для него специфические настройки или команды. Среда разработки ESP-IDF для чипов семейств ESP32 (Espressif IoT Development Framework) основана на компиляторе GCC, поэтому поддерживаются многие стандартные директивы #pragma. В этой статье (перевод [1]) приведены примеры директив #pragma, часто используемых в разработке ESP32.

[Общая информация по директивам #pragma ESP32]

#pragma это стандартная для C/C++ директива препроцессора, которая позволяет передать компилятору специальные инструкции или конфигурации. Её поведение зависит от компилятора и используемой платформы, поэтому разные компиляторы могут поддерживать разный набор директив #pragma.

В разработке ESP32 директива #pragma может использоваться в следующих сценариях:

• Управление оптимизацией для отдельных кусков кода (функций).
• Управление конфигурацией памяти (memory layout).
• Разрешение или запрет определенных предупреждений компилятора (warnings).
• Конфигурирование поведения компилятора.

[Часто используемые директивы #pragma]

#pragma once

Эта директива выполняет функцию защиты содержимого заголовка, что обычно реализовывалось блоком из #ifndef/#define/#endif. Директива #pragma once говорит компилятору, что этот заголовочный файл следует подключать только один раз, чтобы избежать возможных повторных определений.

#pragma GCC optimize ("уровень_оптимизации")

Эта директива указывает компилятору уровень оптимизации для функции или для всего файла кода. Пример использования:

#pragma GCC optimize ("O3")

GCC поддерживает несколько уровней оптимизации кода, вы можете выбрать необходимый уровень в зависимости от ваших текущих потребностей.

O0: нет оптимизации. Этот вариант полезен для отладки кода.
O1: базовая оптимизация, удалит ненужный код для повышения эффективности использования памяти, без увеличения времени компиляции.
O2: средняя оптимизация, применяются более агрессивные методы оптимизации наподобие замены вызовов встраиванием кода (inlining), оптимизации циклов, и т. п.
O3: высокая степень оптимизации, дает максимальную производительность, однако может увеличиться время компиляции.
Os: оптимизация с акцентом на уменьшение размера кода. Этот вариант полезен для ситуации, когда существуют жесткие ограничения на размер памяти.
Ofast: агрессивная оптимизация, пропускающая некоторые стандартные спецификации (наподобие математической точности) с целью ускорения выполнения кода.

#pragma diagnostic

Это мощный инструмент управления сообщениями предупреждения компилятора (warning messages). Используя команды push, pop, ignored, warning, error и default разработчики могут гибко управлять предупреждениями, улучшая качество кода и упрощая отладку.

Назначение команд директивы #pragma diagnostic:

push: сохраняет текущие настройки для diagnostic; последующие изменения поведения обработки предупреждений будут действовать до восстановления состояния командой pop.
pop: восстановит сохраненные настройки diagnostic, которые ранее были сохранены командой push. Обычно парно используются комбинации push/pop для локальной настройки поведения обработки предупреждений.
ignored: запрещает определенные предупреждения так, что они не появляются во время компиляции.
warning: установит определенную диагностику как предупреждение (warning). Когда условие появления warning удовлетворено, компилятор покажет предупреждающее сообщение.
error: обработать определенную диагностику как ошибку (error). Когда условие появления warning удовлетворено, компилятор обработает эту ситуацию как ошибку и остановит компиляцию.
default: сброс настроек компилятора по обработке предупреждений в состояние по умолчанию. Обычно используется после того, как игнорируемые предупреждения обработаны, или после других настроек, чтобы сбросить поведение обработки предупреждений.
no_ignored: восстанавливает игнорируемые предупреждения и снова разрешает их обработку как предупреждения (warnings) или ошибки (errors).

Пример использования:

#pragma GCC diagnostic push
// Следующая директива запретит предупреждение о том,
// что переменная не используется (unused variable):
#pragma GCC diagnostic ignored "-Wunused-variable"

void
func() { int unused_var = 10; // здесь предупреждение не появится }

// Восстановление предыдущих настроек обработки
// диагностических сообщений:
#pragma GCC diagnostic pop

Еще пример:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" /*-------------------------------- last block: affect all 32 bits of (c) */ switch(length) /* all the case statements fall through */ { case 12: c+=((uint32_t)k[11]) << 24; case 11: c+=((uint32_t)k[10]) << 16; case 10: c+=((uint32_t)k[9]) << 8; case 9 : c+=k[8]; case 8 : b+=((uint32_t)k[7]) << 24; case 7 : b+=((uint32_t)k[6]) << 16; case 6 : b+=((uint32_t)k[5]) << 8; case 5 : b+=k[4]; case 4 : a+=((uint32_t)k[3]) << 24; case 3 : a+=((uint32_t)k[2]) << 16; case 2 : a+=((uint32_t)k[1]) << 8; case 1 : a+=k[0]; break; case 0 : return c; }
#pragma GCC diagnostic pop

#pragma pack

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

#pragma pack(push, 1): push сохранит текущую настройку выравнивания, здесь 1 установит выравнивание по одному байту. Это означает, что не будут добавлены никакие выравнивающие байты для сохранения четности адреса (или кратности его 2, 4 или 8).
#pragma pack(pop): восстановит предыдущие настройки выравнивания, ранее сохраненные push, что обеспечит отсутствие влияния предыдущей настройки выравнивания на другие определения структур.

Вместе с push вместо 1 может быть указано другое значение для выравнивания адресов. Обычно используют 2 для выравнивания адреса на половину слова (байтовый адрес полей структур в памяти всегда нацело делится на 2), 4 для выравнивания на размер слова (байтовый адрес полей структур в памяти всегда нацело делится на 4), 8 для выравнивания на размер двойного слова (байтовый адрес полей структур в памяти всегда нацело делится на 8).

// Сохранит текущее выравнивание, и установит выравнивание
// на 1 байт (т. е. отключит выравнивание):
#pragma pack(push, 1)
typedef struct { uint8_t id; // 1 байт uint16_t value; // 2 байта (без дополнения) uint32_t timestamp; // 4 байта (без дополнения) } PackedStruct;
// В результате размер структуры получится 1+2+4 = 7 байт.
// Восстановит предыдущие настройки выравнивания:
#pragma pack(pop)

#pragma message

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

#pragma message("Здесь вставляется пользовательское сообщение")

Замечание: директива #pragma message поддерживается компиляторами GCC, Clang, MSVC и другими, однако формат вывода может несколько отличаться у разных компиляторов.

[Заключение]

Директивы #pragma в ESP32 зависят от компилятора GCC, и официальная документация ESP-IDF не приводит явный список всех поддерживаемых директив. Вы можете обратиться к официальной документации GCC [2] для получения дополнительной информации. Обычные директивы #pragma (такие как управление оптимизацией, конфигурация памяти, управление предупреждениями и т. д.) доступны для использования вместе с платформой ESP32.

[Ссылки]

1. ESP32 Pragma Directives Tips site:saludpcb.com.
2. Pragmas Accepted by GCC site:gcc.gnu.org.