Home:ALL Converter>Is it reasonable to use enums instead of #defines for compile-time constants in C?

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

Ask Time:2015-10-20T16:50:26         Author:ein supports Moderator Strike

Json Formatter

I'm coming back to some C development after working in C++ for a while. 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. In C, static constants are not really compile-time constants, and enums may (? or may not?) behave slightly differently.

So, is it reasonable to prefer using enums for constants rather than #defines?

For reference, here's an excellent list of pros and cons of enums, #defines and static consts in C++.

Author:ein supports Moderator Strike,eproduced under the CC 4.0 BY-SA copyright license with a link to the original source and this disclaimer.
Link to original article:https://stackoverflow.com/questions/33232075/is-it-reasonable-to-use-enums-instead-of-defines-for-compile-time-constants-in
Basile Starynkevitch :

The advantage of using 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).\n\nIn particular, the debugger is much more likely to know about FOO from 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...).\n\nBecause of that, I prefer enum { FOO=34 }; over #define FOO 34\n\nAnd there is also a typing advantage. I could get more warnings from the compiler using enum color_en { WHITE, BLACK }; enum color_en color; than using bool isblack;\n\nBTW, 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).",
2015-10-20T09:15:08
user1196549 :

I would stick to using the features for their purpose.\n\nA symbolic parameter, taking a discrete value among a set of alternatives, should be represented as an enum member.\n\nA numerical parameter, such as array size or numerical tolerance, should be represented as a const variable. 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. I now even unorthodoxically opt for defined symbols using the same casing scheme as other identifiers.\n\nThe 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\n\n#define IdleMask 1\n#define WaitingMask 2\n#define BusyMask (IdleMask | WaitingMask)\nenum Modes { Idle= IdleMask, Waiting= WaitingMask, Busy= BusyMask };\n\n\nThis 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.",
2015-10-20T09:20:29
jsheaney :

I've been working in embedded systems for over a dozen years and use C primarily. My comments are specific to this field. There are three ways to create constants that have specific implications for these types of applications.\n\n1) #define: macros are resolved by the C preprocessor before the code is presented to the C compiler. 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. The rest disappear and are not presented to the C compiler.\n\nValues defined as macros become literals in C. As such, they do not result in any data storage. There is no data memory location associated with the definition.\n\nMacros 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:\n\n#if HEARTBEAT_TIMER_MS > 0\n StartHeartBeatTimer(HEARTBEAT_TIMER_MS);\n#endif\n\n\n2) Enumerations: Like macro definitions, enumerations do not result in data storage. 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. 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. \n\nUnreferenced 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. 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.\n\nEnumerations 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.\n\nSince enumerations are C language constructs, they are definitely evaluated at compiler time. For example:\n\n#define CONFIG_BIT_POS 0\n#define CONFIG_BIT_MASK (1 << CONFIG_BIT_POS)\n\n\nCONFIG_BIT_MASK is a text substitute for (1 << CONFIG_BIT_POS). When (1 << CONFIG_BIT_POS) is presented to the C compiler, it may or may not produce the literal 1.\n\nenum {\n CONFIG_BIT_POS = 0,\n CONFIG_BIT_MASK = (1 << CONFIG_BIT_POS)\n};\n\n\nIn this case CONFIG_BIT_MASK is evaluated and becomes the literal value 1.\n\nFinally, 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).\n\n3) const: This is a C language construct that makes a data value read only. 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). (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. That means that it has type, which defines that storage at a known location. It can be the argument of sizeof(). It can be read at runtime by an external application or a debugger.\n\nThese comments are targeted at embedded applications. Obviously, with a desktop application, everything is in RAM and much of this doesn't really apply. In that context, const makes more sense.",
2017-05-14T19:39:58
BЈовић :

\n is it reasonable to prefer using enums for constants rather than #define's ?\n\n\nIf you like. Enums behave like integers.\n\nBut 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.\n\nFor example :\n\nconst int MY_CONSTANT = 7;\n\n\ninstead of\n\n#define MY_CONSTANT 7\n\n\nor \n\nenum\n{\n MY_CONSTANT = 7\n};\n\n\n\n\nBTW My answer was related to C++. I am not sure if it applies to C.",
2015-10-20T08:54:33
Paul Ogilvie :

A const int MY_CONSTANT = 7; will take up storage; an enum or #define does not.\n\nWith a #define you can use any (integer) value, for example #define IO_PORT 0xb3\n\nWith an enum you let the compiler assign the numbers, which can be a lot easier if the values don't matter that much:\n\nenum {\n MENU_CHOICE_START = 1,\n MENU_CHOICE_NEXT,\n ...\n};\n",
2015-10-20T09:05:12
Mariusz Jaskółka :

Nowadays, in C++ there is no real good reason to use #define for compile-time constants. On the other hand, there are good reasons to use enums or enum classes instead. First and most important - they are much more readable during debugging.\n\nIn C you may want to explicitly choose underlying type, which is impossible with enums. That might be a reason to use defines or consts. But enums should be strongly prefered.\n\nRuntime 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)).",
2019-08-08T10:43:39
yy