简体   繁体   English

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

[英]Is it reasonable to use enums instead of #defines for compile-time constants in C?

I'm coming back to some C development after working in C++ for a while. 在使用C ++一段时间后,我回到了一些C开发阶段。 I've gotten it into my head that macros should be avoided when not necessary in favor of making the compiler do more work for you at compile-time. 我已经明白,如果没有必要支持让编译器在编译时为你做更多工作,应该避免使用宏。 So, for constant values, in C++ I would use static const variables, or C++11 enum classes for the nice scoping. 因此,对于常量值,在C ++中我会使用静态const变量,或者使用C ++ 11枚举类来实现更好的范围。 In C, static constants are not really compile-time constants, and enums may (? or may not?) behave slightly differently. 在C中,静态常量不是真正的编译时常量,枚举可能(?或可能不?)的行为略有不同。

So, is it reasonable to prefer using enums for constants rather than #defines? 那么,更喜欢使用枚举作为常量而不是#defines是否合理?

For reference, here's an excellent list of pros and cons of enums, #defines and static consts in C++ . 作为参考,这里有一个很好的列表,包括枚举,#define和C ++中的静态consts

The advantage of using enum { FOO=34 }; 使用enum { FOO=34 };的优点enum { FOO=34 }; over #define FOO 34 is that macros are preprocessed, so in principle the compiler don't really see them (in practice, the compiler does see them; recent GCC has a sophisticated infrastructure to give from what macro expansion some internal abstract syntax tree is coming). over #define FOO 34是宏被预处理的,所以原则上编译器并没有真正看到它们(实际上,编译器确实看到了它们;最近的GCC有一个复杂的基础设施来从宏扩展中给出一些内部抽象语法树是未来)。

In particular, the debugger is much more likely to know about FOO from enum { FOO=34 }; 特别是,调试器更有可能从enum { FOO=34 };了解FOO enum { FOO=34 }; than from #define FOO 34 (but again, this is not always true in practice; sometimes, the debugger is clever enough to be able to expand macros...). 而不是#define FOO 34 (但同样,在实践中并不总是如此;有时,调试器足够聪明,能够扩展宏......)。

Because of that, I prefer enum { FOO=34 }; 因此,我更喜欢enum { FOO=34 }; over #define FOO 34 #define FOO 34

And there is also a typing advantage. 而且还有打字优势。 I could get more warnings from the compiler using 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; enum color_en { WHITE, BLACK }; enum color_en color; than using bool isblack; 比使用bool isblack;

BTW, static const int FOO=37; BTW, static const int FOO=37; is usually known by the debugger but the compiler might optimize it (so that no memory location is used for it; it might be just an immediate operand inside some instruction in the machine code ). 通常由调试器知道,但编译器可能会对其进行优化(因此不会使用任何内存位置;它可能只是机器代码中某些指令内的一个立即操作数)。

I would stick to using the features for their purpose. 我会坚持使用这些功能用于他们的目的。

A symbolic parameter, taking a discrete value among a set of alternatives, should be represented as an enum member. 在一组备选方案中采用离散值的符号参数应表示为枚举成员。

A numerical parameter, such as array size or numerical tolerance, should be represented as a const variable. 数值参数(如数组大小或数字容差)应表示为const变量。 Unfortunately, C has no proper construct to declare a compile-time constant (like Pascal had), and I would tend to say that a defined symbol is equally acceptable. 不幸的是,C没有适当的构造来声明编译时常量(就像Pascal那样),我倾向于说定义的符号同样可以接受。 I now even unorthodoxically opt for defined symbols using the same casing scheme as other identifiers. 我现在甚至非正统地选择使用与其他标识符相同的套管方案的定义符号。

The enumerations with explicitly assigned values, such as binary masks, are even more interesting. 具有显式指定值的枚举(例如二进制掩码)更加有趣。 At the risk of looking picky, I would consider to use declared constants, like 冒着挑剔的风险,我会考虑使用声明的常量,比如

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

This said, I wouldn't care so much about easing the compiler's task when you see how easily they handle the monstrous pieces of code that they receive daily. 这就是说,当你看到他们如何轻松地处理他们每天收到的巨大代码时,我不会太在意放宽编译器的任务。

is it reasonable to prefer using enums for constants rather than #define's ? 更喜欢使用常量枚举而不是#define?是否合理?

If you like. 如果你喜欢。 Enums behave like integers. 枚举表现得像整数。

But I would still prefer constants, instead of both enums and macros. 但我仍然喜欢常量,而不是枚举和宏。 Constants provide type-safety, and they can be of any type. 常量提供类型安全性,它们可以是任何类型。 Enums can be only integers, and macros do not respect type safety. 枚举只能是整数,宏不尊重类型安全。

For example : 例如 :

const int MY_CONSTANT = 7;

instead of 代替

#define MY_CONSTANT 7

or 要么

enum
{
  MY_CONSTANT = 7
};

BTW My answer was related to C++. BTW我的回答与C ++有关。 I am not sure if it applies to C. 我不确定它是否适用于C.

A const int MY_CONSTANT = 7; 一个const int MY_CONSTANT = 7; will take up storage; 将占用存储; an enum or #define does not. 枚举或#define不会。

With a #define you can use any (integer) value, for example #define IO_PORT 0xb3 使用#define您可以使用任何(整数)值,例如#define IO_PORT 0xb3

With an enum you let the compiler assign the numbers, which can be a lot easier if the values don't matter that much: 使用枚举,您可以让编译器分配数字,如果值无关紧要,这可以更容易:

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

I've been working in embedded systems for over a dozen years and use C primarily. 我已经在嵌入式系统中工作了十几年,主要使用C语言。 My comments are specific to this field. 我的评论是针对这个领域的。 There are three ways to create constants that have specific implications for these types of applications. 有三种方法可以创建对这些类型的应用程序具有特定含义的常量。

1) #define: macros are resolved by the C preprocessor before the code is presented to the C compiler. 1)#define:在将代码呈现给C编译器之前,C预处理器会解析宏。 When you look at headers files provided by processor vendors, they typically have thousands of macros defining access to the processor registers. 当您查看处理器供应商提供的头文件时,它们通常有数千个宏定义对处理器寄存器的访问。 You invoke a subset of them in your code and they become memory accesses in your C source code. 您在代码中调用它们的一部分,它们将成为C源代码中的内存访问。 The rest disappear and are not presented to the C compiler. 其余的消失,不会呈现给C编译器。

Values defined as macros become literals in C. As such, they do not result in any data storage. 定义为宏的值将成为C中的文字。因此,它们不会导致任何数据存储。 There is no data memory location associated with the definition. 没有与定义关联的数据存储位置。

Macros can be used in conditional compilation. 宏可用于条件编译。 If you want to strip out code based on feature configuration then you have to use macro definitions. 如果要根据功能配置删除代码,则必须使用宏定义。 For example: 例如:

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

2) Enumerations: Like macro definitions, enumerations do not result in data storage. 2)枚​​举:与宏定义一样,枚举不会导致数据存储。 They become literals. 他们成为文字。 Unlike macro definitions, they are not stripped by the preprocessor. 与宏定义不同,它们不会被预处理器剥离。 They are C language constructs and will appear in preprocessed source code. 它们是C语言结构,将出现在预处理的源代码中。 They cannot be used to strip code via conditional compilation. 它们不能用于通过条件编译剥离代码。 They cannot be tested for existence at compile time or runtime. 它们无法在编译时或运行时进行测试。 Values can only be involved in runtime conditionals as literals. 值只能作为文字参与运行时条件。

Unreferenced enumerations won't exist at all in compiled code. 编译代码中根本不存在未引用的枚举。 On the other hand, compilers may provide warnings if enumerated values are not handled in a switch statement. 另一方面,如果未在switch语句中处理枚举值,则编译器可能会提供警告。 If the purpose of the constant is to produce a value that must be handled logically then only an enumeration can provide the degree of safety that comes with the use of switch statements. 如果常量的目的是产生一个必须逻辑处理的值,那么只有枚举才能提供使用switch语句所带来的安全程度。

Enumerations also have an auto-increment feature, so if the purpose of the constant is to be used as an constant index into an array then I would always go with an enumeration to avoid unused slots. 枚举也有一个自动增量功能,所以如果常量的目的是用作数组的常量索引,那么我总是使用枚举来避免未使用的槽。 In fact, the enumeration itself can produce a constant representing a number of items that can be used in an array declaration. 实际上,枚举本身可以生成一个常量,表示可以在数组声明中使用的多个项。

Since enumerations are C language constructs, they are definitely evaluated at compiler time. 由于枚举是C语言结构,因此它们肯定在编译器时进行评估。 For example: 例如:

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

CONFIG_BIT_MASK is a text substitute for (1 << CONFIG_BIT_POS). CONFIG_BIT_MASK是(1 << CONFIG_BIT_POS)的文本替代。 When (1 << CONFIG_BIT_POS) is presented to the C compiler, it may or may not produce the literal 1. 当(1 << CONFIG_BIT_POS)呈现给C编译器时,它可能会也可能不会产生文字1。

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

In this case CONFIG_BIT_MASK is evaluated and becomes the literal value 1. 在这种情况下,将评估CONFIG_BIT_MASK并将其作为文字值1。

Finally, I would add that macro definitions can be combined to produce other code symbols, but cannot be used to create other macro definitions. 最后,我想补充一点,宏定义可以组合起来产生其他代码符号,但不能用于创建其他宏定义。 That means that if the constant name must be derived then it can only be an enumeration created by a combination of macro symbols or macro expansion, such as with list macros (X macros). 这意味着如果必须派生常量名称,那么它只能是由宏符号或宏扩展的组合创建的枚举,例如列表宏(X宏)。

3) const: This is a C language construct that makes a data value read only. 3)const:这是一个C语言结构,它使数据值只读。 In embedded applications this has an important role when applied to static or global data: it moves the data from RAM into ROM (typically, flash). 在嵌入式应用程序中,当应用于静态或全局数据时,它具有重要作用:它将数据从RAM移动到ROM(通常是闪存)。 (It does not have this effect on locals or auto variables because they are created on the stack or in registers at runtime.) C compilers can optimize it away, but certainly this can be prevented, so aside from this caveat, const data actually takes up storage in read only memory at runtime. (它对本地或自动变量没有这种影响,因为它们是在堆栈上或在运行时在寄存器中创建的。)C编译器可以对它进行优化,但当然可以防止这种情况,所以除了这个警告,const数据实际上需要在运行时在只读存储器中存储。 That means that it has type, which defines that storage at a known location. 这意味着它具有类型,它定义了已知位置的存储。 It can be the argument of sizeof(). 它可以是sizeof()的参数。 It can be read at runtime by an external application or a debugger. 它可以在运行时由外部应用程序或调试器读取。

These comments are targeted at embedded applications. 这些注释针对嵌入式应用程序。 Obviously, with a desktop application, everything is in RAM and much of this doesn't really apply. 显然,对于桌面应用程序,一切都在RAM中,其中大部分都不适用。 In that context, const makes more sense. 在这种情况下,const更有意义。

The TL;DR answer is that it doesn't often matter at all if you use #define or enum . TL; DR答案是,如果你使用#defineenum ,它根本不重要。

There are however some subtle differences. 然而,有一些微妙的差异。

The main problem with enums is that you can't change the type. 枚举的主要问题是您无法更改类型。 If you use enumeration constants such as enum { FALSE, TRUE }; 如果使用枚举常量,例如enum { FALSE, TRUE }; , then those constants will always be of type int . ,那些常量将始终是int类型。

This might be problematic if you need unsigned constants, or constants of a different size than sizeof(int) . 如果您需要无符号常量或大小与sizeof(int)不同的常量,则可能会出现问题。 Signed integers may cause subtle bugs if you need to do bitwise operations, because mixing those with negative numbers doesn't make any sense in 99% of the cases. 如果你需要进行按位运算,有符号整数可能会导致细微的错误,因为在99%的情况下,将那些带负数的情况混合起来没有任何意义。

With macros however, you can specify any type: 但是,使用宏,您可以指定任何类型:

#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

The downside is that you don't have to option to actually define a type with macros, which you could do with enums. 缺点是您无需选择实际定义带有宏的类型,您可以使用枚举。 enums give a slight bit more of type safety, but only if you typedef them: typedef enum {FALSE, TRUE} BOOL; 枚举提供了更多的类型安全性,但只有当你键入它们时: typedef enum {FALSE, TRUE} BOOL; . C doesn't have much type safety at all, but good compilers or external static analysis tools can detect & warn for type issues when trying to convert to/from enum type by accident. C根本没有太多的类型安全性,但是好的编译器或外部静态分析工具可以在尝试意外转换为枚举类型时检测并警告类型问题。

Another oddity with that though, is that "BOOL" is an enum variable. 然而,另一个奇怪的是,“BOOL”是一个枚举变量。 enum variables, unlike enum constants, have no guarantee of which integer type they correspond to. 与枚举常量不同,枚举变量不保证它们对应于哪种整数类型。 You just know that it will be some kind of integer type large enough to fit all values of the corresponding enumeration constants. 你只知道它将是一种足够大的整数类型,以适应相应枚举常量的所有值。 This might be a very bad thing in case the size of the enum matters. 如果枚举的大小很重要,这可能是一件非常糟糕的事情。

And of course, enums have the advantage that you can declare them at local scope, so you don't unnecessarily clutter down the global namespace when you don't need to. 当然,枚举的优点是可以在本地范围内声明它们,因此在不需要时不会不必要地混淆全局命名空间。

Nowadays, in C++ there is no real good reason to use #define for compile-time constants. 如今,在C++ ,没有真正的理由将#define用于编译时常量。 On the other hand, there are good reasons to use enum s or enum class es instead. 另一方面,有充分的理由使用enumenum class First and most important - they are much more readable during debugging. 首先也是最重要的 - 它们在调试过程中更具可读性。

In C you may want to explicitly choose underlying type, which is impossible with enum s. C您可能希望明确选择基础类型,这对于enum是不可能的。 That might be a reason to use define s or const s. 这可能是使用define s或const的原因。 But enums should be strongly prefered. 但应该强烈赞成枚举。

Runtime overhead is not a problem - in modern compilers there won't be any difference in generated machine code (as long as sizeof(the_enum)=sizeof(the_type_used_by_define_based_values) ). 运行时开销不是问题 - 在现代编译器中,生成的机器代码不会有任何差异(只要sizeof(the_enum)=sizeof(the_type_used_by_define_based_values) )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM