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).
The use of an enumeration constant (enum) has many advantages over using the traditional symbolic constant style of #define. These advantages include a lower maintenance requirement, improved program readability, and better debugging capability.
One of the differences between the two is that #define is a pre-processor directive while enum is part of the actual C language. #define statements are processed by the compiler before the first line of C code is even looked at!
Enumeration (or enum) is a user defined data type in C. It is mainly used to assign names to integral constants, the names make a program easy to read and maintain. Hereby mistake, the state of wed is 2, it should be 3.
In terms of readability, enumerations make better constants than macros, because related values are grouped together. In addition, enum defines a new type, so the readers of your program would have easier time figuring out what can be passed to the corresponding parameter.
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
).
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With