在C中使用枚举而不是#defines用于编译时常量是否合理?

在使用C ++一段时间后,我回到了一些C开发阶段。 我已经明白,如果没有必要支持让编译器在编译时为你做更多工作,应该避免使用宏。 因此,对于常量值,在C ++中我会使用静态const变量,或者使用C ++ 11枚举类来实现优秀的范围。 在C中,静态常量不是真正的编译时常量,枚举可能(?或可能不?)的行为略有不同。

那么,更喜欢使用枚举作为常量而不是#defines是否合理?

作为参考,这里有一个很好的列表,包括枚举,#define和C ++中的静态consts 。

使用enum { FOO=34 };的优点enum { FOO=34 }; over #define FOO 34是宏被预处理的,所以原则上编译器并没有真正看到它们(实际上,编译器确实看到它们;最近的GCC有一个复杂的基础设施来从宏扩展中给出一些内部抽象语法树是未来)。

特别是,调试器更有可能从enum { FOO=34 };了解FOO enum { FOO=34 }; 而不是#define FOO 34 (但同样,在实践中并非总是如此;有时,调试器足够聪明,能够扩展宏……)。

因此,我更喜欢enum { FOO=34 };#define FOO 34

而且还有打字优势。 我可以使用enum color_en { WHITE, BLACK }; enum color_en color;从编译器获得更多警告enum color_en { WHITE, BLACK }; enum color_en color; enum color_en { WHITE, BLACK }; enum color_en color; 比使用bool isblack;

BTW, static const int FOO=37; 通常由调试器知道,但编译器可能会优化它(因此没有使用内存位置;它可能只是机器代码中某些指令内的一个立即操作数)。

我会坚持使用这些function用于他们的目的。

在一组备选方案中采用离散值的符号参数应表示为枚举成员。

数值参数(如数组大小或数字容差)应表示为const变量。 不幸的是,C没有适当的构造来声明编译时常量(就像Pascal那样),我倾向于说定义的符号同样可以接受。 我现在甚至非正统地选择使用与其他标识符相同的套管方案的定义符号。

具有显式指定值的枚举(例如二进制掩码)更加有趣。 冒着挑剔的风险,我会考虑使用声明的常量,比如

 #define IdleMask 1 #define WaitingMask 2 #define BusyMask (IdleMask | WaitingMask) enum Modes { Idle= IdleMask, Waiting= WaitingMask, Busy= BusyMask }; 

这就是说,当你看到他们能够轻松处理他们每天收到的巨大代码时,我不会太在意放宽编译器的任务。

是否合理使用枚举作为常量而不是#define?

如果你喜欢。 枚举表现得像整数。

但我仍然更喜欢常量,而不是枚举和宏。 常量提供类型安全性,它们可以是任何类型。 枚举只能是整数,宏不尊重类型安全。

例如 :

 const int MY_CONSTANT = 7; 

代替

 #define MY_CONSTANT 7 

要么

 enum { MY_CONSTANT = 7 }; 

BTW我的回答与C ++有关。 我不确定它是否适用于C.

一个const int MY_CONSTANT = 7; 将占用存储; 枚举或#define不会。

使用#define您可以使用任何(整数)值,例如#define IO_PORT 0xb3

使用枚举,您可以让编译器分配数字,如果值无关紧要,这可以更容易:

 enum { MENU_CHOICE_START = 1, MENU_CHOICE_NEXT, ... }; 

TL; DR答案是,如果你使用#defineenum ,它根本不重要。

然而,有一些微妙的差异。

枚举的主要问题是您无法更改类型。 如果使用枚举常量,例如enum { FALSE, TRUE }; ,那些常量将始终是int类型。

如果您需要无符号常量或大小不同于sizeof(int)常量,这可能会有问题。 如果你需要进行按位运算,有符号整数可能会导致细微的错误,因为在99%的情况下,将那些带负数的情况混合起来没有任何意义。

但是,使用宏,您可以指定任何类型:

 #define X 0 // int type #define X 0u // unsigned int type #define X 0ul // unsigned long type #define X ((uint8_t)0) // uint8_t type 

缺点是您无需选择实际定义带有宏的类型,您可以使用枚举。 枚举提供了更多的类型安全性,但只有当你键入它们时: typedef enum {FALSE, TRUE} BOOL; 。 C根本没有太多的类型安全性,但是好的编译器或外部静态分析工具可以在尝试意外转换为枚举类型时检测并警告类型问题。

然而,另一个奇怪的是,“BOOL”是一个枚举变量。 与枚举常量不同,枚举变量不保证它们对应于哪种整数类型。 你只知道它将是某种足够大的整数类型,以适应相应枚举常量的所有值。 如果枚举的大小很重要,这可能是一件非常糟糕的事情。

当然,枚举的优势在于您可以在本地范围内声明它们,因此在不需要时不会不必要地混淆全局命名空间。

我已经在嵌入式系统中工作了十几年,主要使用C语言。 我的评论是针对这个领域的。 有三种方法可以创建对这些类型的应用程序具有特定含义的常量。

1)#define:在将代码呈现给C编译器之前,C预处理器会解析宏。 当您查看处理器供应商提供的头文件时,它们通常有数千个宏定义对处理器寄存器的访问。 您在代码中调用它们的一部分,它们将成为C源代码中的内存访问。 其余的消失,不会呈现给C编译器。

定义为宏的值将成为C中的文字。因此,它们不会导致任何数据存储。 没有与定义关联的数据存储位置。

宏可用于条件编译。 如果要根据function配置删除代码,则必须使用宏定义。 例如:

 #if HEARTBEAT_TIMER_MS > 0 StartHeartBeatTimer(HEARTBEAT_TIMER_MS); #endif 

2)枚​​举:与宏定义一样,枚举不会导致数据存储。 他们成为文字。 与宏定义不同,它们不会被预处理器剥离。 它们是C语言结构,将出现在预处理的源代码中。 它们不能用于通过条件编译剥离代码。 它们无法在编译时或运行时进行测试。 值只能作为文字参与运行时条件。

编译代码中根本不存在未引用的枚举。 另一方面,如果未在switch语句中处理枚举值,则编译器可能会提供警告。 如果常量的目的是产生必须逻辑处理的值,那么只有枚举才能提供使用switch语句所带来的安全程度。

枚举也有一个自动增量function,所以如果常量的目的是用作数组的常量索引,那么我总是使用枚举来避免未使用的槽。 实际上,枚举本身可以生成一个常量,表示可以在数组声明中使用的多个项。

由于枚举是C语言结构,因此它们肯定在编译器时进行评估。 例如:

 #define CONFIG_BIT_POS 0 #define CONFIG_BIT_MASK (1 << CONFIG_BIT_POS) 

CONFIG_BIT_MASK是(1 << CONFIG_BIT_POS)的文本替代。 当(1 << CONFIG_BIT_POS)呈现给C编译器时,它可能会也可能不会产生文字1。

 enum { CONFIG_BIT_POS = 0, CONFIG_BIT_MASK = (1 << CONFIG_BIT_POS) }; 

在这种情况下,将评估CONFIG_BIT_MASK并将其作为文字值1。

最后,我想补充一点,宏定义可以组合起来产生其他代码符号,但不能用于创建其他宏定义。 这意味着如果必须派生常量名称,那么它只能是由宏符号或宏扩展的组合创建的枚举,例如列表宏(X宏)。

3)const:这是一个C语言结构,它使数据值只读。 在嵌入式应用程序中,当应用于静态或全局数据时,它具有重要作用:它将数据从RAM移动到ROM(通常是闪存)。 (它对本地或自动变量没有这种影响,因为它们是在堆栈上或运行时在寄存器中创建的。)C编译器可以对它进行优化,但当然可以防止这种情况,所以除了这个警告,const数据实际上需要在运行时在只读存储器中存储。 这意味着它具有类型,它定义了已知位置的存储。 它可以是sizeof()的参数。 它可以在运行时由外部应用程序或调试器读取。

这些注释针对嵌入式应用程序。 显然,对于桌面应用程序,一切都在RAM中,其中大部分都不适用。 在这种情况下,const更有意义。