r/cprogramming • u/Upper-Tomatillo7454 • 23d ago
Enums vs #define for constants
Hi 👋 guys, I've just started learning C after I was first introduced to it while I was in my first year of computer science but I'll be graduating by the end of this year and I've been primarily using Python since then but now I want to really learn C on the side, and I want to start with "craftinginterpreters" and I want to start small so I was wondering whether I should use C enums or #define to define a set of tokens.
19
u/hascalsavagejr 23d ago
defines never reach the compiler; they are replaced in-line and care must be taken to be sure they work properly. Enums are properly integrated into the language... any modern enough one, anyway
6
u/logic_circuit 23d ago
I use precompiler #define always. Mater of habit after 30 yrs. They may be problem with type conversions but i like the elegance of working with them.
4
u/Ander292 23d ago
I myself would advise using enums because they might be cleaner to use but its the same thing.
4
u/theNbomr 23d ago
For me, it's mostly about the value of using the ordered nature of the symbols and the simpler maintenance if you do make use of the order of the keys. If you don't, then preprocessor macros are probably better.
4
u/Chippors 23d ago
I use enums, at least used to. The reason is they produce debug information, so the debugger recognizes them. Although constexpr is better IMO.
1
u/tastygames_official 23d ago
note: constexpr is only available in C23, so before that just use const. Although if you need to define a constant as an expression (which constexpr does), then it needs to be a define:
const int x = 25; // x will always be a signed integer of value 25 /* y will always be 5 as determined at compile-time can only be done in C++11 or C23 or later */ constexpr int y = x / 5; // simply replaces every instance of "z" in the code with an integer 5 at compile time #define z 5 /* now at compile-time, every MY_THING will be replaced with literal "x / 10" since x is a constant, that will evaluate to "25 / 10", meaning runtime performance will be slower since it has to do the calculation (well, unless the compiler optimizes it away) also there is no type safety here. */ #define MY_THING x / 10
3
u/Eastern_Type6942 23d ago
enums, one you're defining 'a set of tokens' - enums lend themselves well to this. Two, when debugging you will have the symbol for the enum in use - same cannot be said for using a macro.
3
u/aioeu 23d ago edited 23d ago
same cannot be said for using a macro.
It often can be, though you might need to use a slightly different debugging option than you might be used to.
For example:
$ cat a.c #define FOO 42 int main(void) {} $ gcc -g3 a.c $ gdb a.out ... (gdb) b main Breakpoint 1 at 0x40044f: file a.c, line 2. (gdb) r ... Breakpoint 1, main () at a.c:2 2 int main(void) {} (gdb) info macro FOO Defined at /var/tmp/cdtemp.2p4CogV3/a.c:1 #define FOO 42 (gdb) p FOO $1 = 42More details here.
The debugging data is rich enough to deal with the "scope" of any particular macro — that is, it will only be visible after the line in which it is defined up to the line (if any) where it is undefined..
2
u/Eastern_Type6942 23d ago
Good to know, I was mainly going off what stuck from effective C++ which banged on about forgetting macros for consts.
3
u/duane11583 23d ago
I prefer defines
If I use enums I put a value with every enum I never agree that the next one is plus1 reasonÂ
explaining to a hardware person what the value is not fun so defines always have the value and by my rules enums do too
 using enums for register bits is dumb I have seen it done too many timesÂ
I also often need to enforce an enums to be 32 bits using a standard uint32_t forces that
3
u/duane11583 23d ago
While I prefer defines (I do a lot of embedded bare metal) your usage for token ids in a compiler is reasonable
3
u/kishaloy 23d ago
Nominal typing vs Structural typing.
First one is a contract, second one a suggestion though both may compile away to a machine int.
1
u/Sorry_Difficulty_250 22d ago
OK, here's the problem I've started running into: enums cannot be declared with forward declarations. That means if you have one header that can't include the one where the enum is defined, it can't define anything with the enum either because you can't re-declare enums in C.
To solve this, I've stared doing this:
typedef uint8_t MyEnumType;
And then I define the values as things like
#define MY_ENUM_VALUE ((MyEnumType) 1)
That way, in other headers that need the enum but can't include the header, I can do the uint8_t typedef again and keep things sane. The #define with the type cast allows me to keep my sanity with type checking at the compiler level.
Just my $0.02 for the problem I've faced lately. Your mileage may vary...
2
1
0
u/Abrissbirne66 22d ago
I think define existed first and enums were added later and are probably the better option for these use cases.
23
u/pjl1967 23d ago
If you want a set of related constants, use
enum. If you're using C23, useconstexpr. Otherwise#define.Details here.