Применение enum в IAR вместо констант define Печать
Добавил(а) microsin   

На языке C константы времени компиляции можно задавать двумя способами: с помощью макроопределения #define, и с помощью перечисления enum.

Пример задания констант для переменной с помощью #define:

//Состояния калибровки, значения для переменной calibr_state:
#define CALIBR_NOT_ACTIVE             0
#define CALIBR_WAIT_PRESS_CALIB_KZ    1
#define CALIBR_INIT_KZ                2
#define CALIBR_WAIT_DSP_DONE_KZ       3
#define CALIBR_WAIT_PRESS_CALIB_XX    4
#define CALIBR_INIT_XX                5
#define CALIBR_WAIT_DSP_DONE_XX       6
#define CALIBR_WAIT_PRESS_CALIB_ACH   7 //пока не используется
#define CALIBR_INIT_ACH               8
#define CALIBR_WAIT_DSP_DONE_ACH      9
#define CALIBR_INIT_SAVE              10
#define CALIBR_WAIT_DSP_DONE_SAVE     11
#define CALIBR_ERR_KZ                 12
#define CALIBR_ERR_XX                 13
 
extern unsigned char calibr_state;

Пример задания констант для переменной с помощью enum:

//состояния калибровки, переменная calibr_state
typedef enum
{
   CALIBR_NOT_ACTIVE = 0,         // 0
   CALIBR_WAIT_PRESS_CALIB_KZ,    // 1
   CALIBR_INIT_KZ,                // 2
   CALIBR_WAIT_DSP_DONE_KZ,       // 3
   CALIBR_WAIT_PRESS_CALIB_XX,    // 4
   CALIBR_INIT_XX,                // 5
   CALIBR_WAIT_DSP_DONE_XX,       // 6
   CALIBR_WAIT_PRESS_CALIB_ACH,   // 7 пока не используется
   CALIBR_INIT_ACH,               // 8
   CALIBR_WAIT_DSP_DONE_ACH,      // 9
   CALIBR_INIT_SAVE,              // 10
   CALIBR_WAIT_DSP_DONE_SAVE,     // 11
   CALIBR_ERR_KZ,                 // 12
   CALIBR_ERR_XX                  // 13
}CALIBRSTATE;
 
extern CALIBRSTATE calibr_state;

С точки зрения генерации кода оба варианта задания констант идентичны - в том случае, если переменная calibr_state имеет одинаковый размер (в этом примере unsigned char, 1 байт). Другими словами, использование enum не дает лишних затрат ни по размеру кода (FLASH), ни по размеру используемой памяти (RAM). Однако вариант с enum предпочтительнее с точки зрения более строгой проверки типов, и в режиме отладки окно просмотра переменных Watch будет выдавать не числовые, а вполне осмысленные значения. Очевидно, что имеет смысл по возможности всегда использовать enum вместо #define (см. скриншоты).

IAR-constants-watch-define

Рис. 1. Отображение значения переменной в случае использования констант #define.

IAR-constants-watch-enum

Рис. 2. Отображение значения переменной в случае использования констант enum.

Языковые расширения системы программирования IAR (IAR Systems language extensions) разрешают Вам сначала объявить имя enum и далее разрешить его, указать заключенный в фигурные скобки список. Синтаксис использования enum довольно прост - можно просто не указывать значения для констант, тогда компилятор их присвоит автоматически в порядке возрастания, а можно для каждой константы указать конкретное необходимое значение:

//Коды ошибок.
typedef enum
{
   CALIBR_ERR_MASK      = (0x0F << 4),
   CALIBR_ERR_FREQ      = (1    << 4),
   CALIBR_ERR_U         = (2    << 4),
   CALIBR_ERR_PROBES_XX = (3    << 4),
   CALIBR_ERR_PROBES_KZ = (4    << 4),
   CALIBR_ERR_UNKNOWN   = (0x0F << 4)
}CALIBRERR;

Наверняка Вы заметили, что синтаксис объявления переменной в случае использования enum отличается от #define. Теперь вместо стандартного типа нужно указывать тип enum, и компилятор сам подбирает размер переменной для хранения значения. Т. е. программист теперь явно не управляет типом и размером переменной. Как же определить, какой конкретно будет использоваться тип переменной, которая будет хранить константу из enum? Вот выдержка из руководства "IAR C/C++ Development Guide" (EWARM_DevelopmentGuide.ENU.pdf):

Компилятор будет использовать самый маленький по размеру тип, который необходим для хранения констант enum, предпочитая тип со знаком (signed) беззнаковому (unsigned). Когда разрешены расширения языка (IAR Systems language extensions), и в программах на C++, константы enum constants и типы могут также быть long, unsigned long, long long, или unsigned long long. Чтобы заставить компилятор использовать тип больше по размеру, чем он автоматически использовал бы, определите в enum константу больше по размеру, чем это необходимо. Например:

/* Запрет использования типа char для переменной enum */
enum Cards
{
   Spade1,
   Spade2,
   DontUseChar=257
};

Кроме того, опция компилятора командной строки --enum_is_int позволяет для переменной enum выделить такой же объем памяти, как и для переменной int (для ARM это 4 байта). При использовании этой опции компилятор выделит под переменную enum как минимум 4 байта памяти, независимо от того, что значения в enum достаточны для типа меньшего размера. Внимание: эта опция не будет рассматривать факт, что тип enum может быть больше, чем тип целого числа. Чтобы установить эту опцию для проекта, добавьте её через меню Project -> Options -> C/C++ Compiler -> Extra Options.

IAR-enum is_int-option

[Особенности использования enum для задания констант]

У enum по сравнению с #define есть недостатки. Нужно следить за размером данных, который назначает компилятор под константы и тип переменной enum. Кроме того, нужно следить за типами и проверками условий. Вот несколько особенностей enum, о которых необходимо помнить.

1. Избегайте логических операций с константами enum, иначе могут появляться предупреждения несоответствия типа Warning[Pe188]. Причина в том, что операторы логики дают в результате своей работы целый тип, а не тот тип, который определен в typedef enum. Задавать битовый маски константами enum тоже плохая идея.

2. Не смешивайте проверки переменных с типом enum с проверками числовых значений. Дело в том, что тип enum синтаксически это уже не просто целое число. Например, if (0 == errorcode) уже не то же самое, что и if (NO_ERROR == errorcode), даже если в enum задано NO_ERROR = 0! Поэтому в проверках enum используйте только enum-константы, не числа.

3. В структурах, для которых важен общий размер (размер структур часто важен для протоколов передачи данных), нельзя применять поля, имеющие тип, заданный через typedef enum. Понятно почему - к примеру, если Вы вдруг поменяете typedef enum, просто добавив туда новое значение, и при этом не обратите внимание на размерность числа, то компилятор может решить выделить под эти поля размер больше чем раньше. В результате Вы будете долго гадать, почему отлаженный протокол вдруг перестал работать.

4. Если определить enum без инициализации константами, то числовые значения перечисления в enum будут задаваться в порядке возрастания, начиная с 0. Поэтому числовое значение от enum можно использовать вместо индекса в массиве, когда нужно какое-либо перекодирование enum в нужное числовое значение.

Вывод: константы enum больше всего подходят для перечислений, у которых не важен размер, и над которыми не надо проводить арифметические и логические действия. Это могут быть последовательные деревья проверок if / else if / else, операторы switch. Не применяйте enum для битовых флагов, задания масок, для полей структур данных.