На языке 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 (см. скриншоты).
Рис. 1. Отображение значения переменной в случае использования констант #define.
Рис. 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.
[Особенности использования 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 для битовых флагов, задания масок, для полей структур данных. |