nowox
nowox

Reputation: 29096

Choosing between enum or define in C?

I noticed that most of the predefined values in libc are written using #define directives. For example, the whence parameter takes an int in fseek where (from my knowledge) an enum would have been better. There are plenty of examples like this that should obviously exist for a reason (other than back-compatibility issues).

So I am wondering in which case is it better to use #define whereas enum is a type-safe alternative more easily discoverable.

As a practical example consider a typedef representing an input/output channel, as it could be the case on the kernel. The gpio can be configured either in input or output. Is it worth to use an enum directive here?

typedef struct gpio {
    size_t port; 
    size_t bit;
    enum { DIR_INPUT, DIR_OUTPUT } direction; // or `bool`?
    bool value;
} gpio;

Notice that the enum can be implemented in three different manners:

i) Type:

typedef enum gpio_direction {   
   DIR_OUTPUT
   DIR_INPUT
} gpio_direction;

ii) Global enum

enum gpio_direction {   
   DIR_OUTPUT
   DIR_INPUT
} gpio_direction;

iii) Anonymous enum (as showed in my example).

Upvotes: 7

Views: 765

Answers (2)

skyking
skyking

Reputation: 14360

While one probably should use other solutions where such are available there are still situations where macros are required. Some are historical reasons, but there are also reasons valid still today.

For example you mention the whence argument to fseek (ie SEEK_SET, SEEK_CUR and `SEEK_END). These are (for historical reasons) specified to be macros in the standard - so the implementor has to define them as macros to be fully compliant, some evil programmer could write (and blame the library for consequences):

#include <stdio.h>

#ifndef SEEK_CUR
int evil = *(int*)0;
#endif

But as far as I can see theres no reason why they couldn't write:

enum __WHENCE_T {
    __SEEK_CUR,
    __SEEK_END,
    __SEEK_SET
};

#define SEEK_CUR __SEEK_CUR
#define SEEK_END __SEEK_END
#define SEEK_SET __SEEK_SET

Another historical reason is that the compiler might have produced more efficient code when using macros. Code that were written back then might still be around and containing constructs that worked better back then (and still works pretty god today).

Modern reasons are if you need to use the values outside of the C-compiler. For example as mentioned if you want to use the value in assembler code (or some other language). All you need to do is to make sure that the header doesn't expand to something (that contain C code) unless it's compiled with a C compiler. For example:

#define NUMBER 42

#ifdef __STDC__
extern int variable;

int function(void);
#endif

if included from assembler the macro NUMBER would still be expandable (and expand to the same thing as for C programs).

Upvotes: 3

Hans Lub
Hans Lub

Reputation: 5678

There are plenty of examples like this that should obviously exist for a reason

One reason is that there is no portable way to refer to a C enum from assembly code. Low-level code usually was (and often still is) written in assembly, so constants used by that low-level code will be specified by a #define rather than with enum.

Another reason is the idiom if (verbosity & WAKE_THE_NEIGHBOURS) where a #defined value is used to specify a bit position.

So I am wondering in which case is it better to use #define whereas enum is a type-safe alternative more easily discoverable

In all other cases I would (today - using #define is also somewhat of a tradition) use an enum, so that if (verbosity == RED) will provoke a warning (if you use e.g. gcc with -Wall).

Upvotes: 5

Related Questions