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

时间:2015-10-20 08:50:26

标签: c enums constants c-preprocessor

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

那么,更喜欢使用常量枚举而不是#defines?

是否合理

供参考,这是excellent list of pros and cons of enums, #defines and static consts in C++

7 个答案:

答案 0 :(得分:7)

使用enum { FOO=34 };而不是#define FOO 34的优势在于宏是预处理的,所以原则上编译器并不真正看到它们(实际上,编译器 >看到它们;最近的GCC有一个复杂的基础设施,可以从宏扩展中提供一些内部抽象语法树。)

特别是,调试器更可能从FOO知道enum { FOO=34 };而不是#define FOO 34(但同样,在实践中并不总是这样;有时,调试器是聪明到能够扩展宏......)。

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

还有打字优势。我可以使用enum color_en { WHITE, BLACK }; enum color_en color;从使用bool isblack;

的编译器获得更多警告

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

答案 1 :(得分:7)

我会坚持将这些功能用于他们的目的。

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

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

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

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

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

答案 2 :(得分:4)

  

更喜欢使用常量枚举而不是#define' s?

是否合理

如果你愿意。枚举表现得像整数。

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

例如:

const int MY_CONSTANT = 7;

而不是

#define MY_CONSTANT 7

enum
{
  MY_CONSTANT = 7
};

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

答案 3 :(得分:4)

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

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

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

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

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

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

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

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

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

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

CONFIG_BIT_MASK是(1&lt;&lt; CONFIG_BIT_POS)的文本替代。当(1&lt;&lt; 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更有意义。

答案 4 :(得分:3)

const int MY_CONSTANT = 7;将占用存储空间;枚举或#define没有。

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

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

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

答案 5 :(得分:2)

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根本没有太多的类型安全性,但是好的编译器或外部静态分析工具可以检测到&amp;在尝试偶然转换为枚举类型时从类型问题发出警告。

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

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

答案 6 :(得分:1)

如今,在C++中没有真正好的理由将#define用于编译时常量。另一方面,有充分的理由改用enumenum class es。首先也是最重要的-在调试过程中它们更具可读性。

C中,您可能需要显式选择基础类型,而对于enum来说,这是不可能的。这可能是使用defineconst的原因。但是应该强烈建议使用枚举。

运行时开销不是问题-在现代编译器中,生成的机器代码不会有任何区别(只要sizeof(the_enum)=sizeof(the_type_used_by_define_based_values))。