7

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).

Community
  • 1
  • 1
nowox
  • 25,978
  • 39
  • 143
  • 293
  • 1
    one problem with `enum` is that enumerators always have type `int`. They can't be used for values outside the range of `int`. Another potential problem in your code is that the enum size may vary between compilers (or even compilations, in theory) , messing with your struct layout. – M.M May 17 '16 at 08:09
  • 1
    I personally would use always a Enum when the value doesn't matter like in state machines or in conditon fields. In this cases I never compare ENUM with int. I always compare ENUM with ENUM. In cases where the actual value matters I would always use #defines. E.G. Hardware configuration register values or Bit fields. – Schafwolle May 17 '16 at 08:14
  • 1
    You are asking what is best practice in embedded systems. libc was written by desktop programmers. There are more things to consider for embedded systems, where it is far more important to be explicit with types. If you aren't asking about embedded systems specifically, then [this](http://stackoverflow.com/questions/1674032/static-const-vs-define-vs-enum) is the duplicate. Overall this topic has been debated endlessly all over SO, you can find plenty of info about it. – Lundin May 17 '16 at 08:35
  • Your latter suggestion is slightly better since you don't really use `fseek` on an embedded device, but again I don't agree with your choice. I am asking about `Why is it better to use enum where `libc` uses #defines for most cases`. I can rename the question if you want to. – nowox May 17 '16 at 08:39
  • Sorry, but I'm not going to re-open the question just to close it with a different duplicate. If there's a consensus that this post isn't a duplicate of [“static const” vs “#define” vs “enum”](http://stackoverflow.com/questions/1674032/static-const-vs-define-vs-enum), then it will get re-opened. I think it is a duplicate. – Lundin May 17 '16 at 08:42
  • 2
    The interesting (and non-duplicate) part of the question is: why then is `#define` used so often? – Hans Lub May 17 '16 at 08:45
  • I can't do that, since I am the person who closed the post. If I re-open a question, I will not be able to close vote on it. – Lundin May 17 '16 at 08:46
  • @HansLub The answer to that is likely mostly subjective though. Unless someone can drag up the coding standard used for libc. – Lundin May 17 '16 at 08:48
  • @HansLub that's a matter of opinion really. I guess some coders don't realize enum is usable for this purpose. Another consideration might be that defines can be used for conditional inclusion but enums can't – M.M May 17 '16 at 10:25
  • @M.M `enum` type detail: [What is the size of an enum in C?](http://stackoverflow.com/q/366017/2410359) – chux - Reinstate Monica May 17 '16 at 14:34
  • With `enum`, code is quieter. with `#define`, CODE IS SHOUTING. – chux - Reinstate Monica May 17 '16 at 14:40

2 Answers2

5

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).

Hans Lub
  • 5,513
  • 1
  • 23
  • 43
  • Well, is it also possible to use `#ifndef _LANGUAGE_ASM \n enum {...} \n #else \n #define ... \n #endif` – nowox May 17 '16 at 08:16
  • The `if (verbosity & WAKE_THE_NEIGHBOURS)` is a very good point – nowox May 17 '16 at 08:48
  • `if (verbosity & WAKE_THE_NEIGHBOURS)` could as well be used with an enum though... except you will get a signed type. Sloppily written `#define` will do the same, so there's no real difference, unless the `#define` is written with care. – Lundin May 17 '16 at 08:58
  • @Lundin: no, `WAKE_THE_NEIGHBOURS` needs a value from the sequence `1,2,4,8,16,..`, while an `enum` will allocate `0,1,2,3,..`. You can get around that with `<<` but what's the point? – Hans Lub May 17 '16 at 09:02
  • @HansLub Nothing prevents you from giving any value to an enum. `typedef enum { WAKE_THE_NEIGHBOURS = 16; .. } wake_t`. This is why your answer doesn't make much sense, as it currently stands. – Lundin May 17 '16 at 09:33
  • 1
    Yes, but you also lose the (rather weak) type-safety: `if (verbosity & RED)` will be just as (il)legitimate as `if (verbosity & WAKE_THE_NEIGHBOURS)` : both need to convert an `enum` to an int, erasing all information about the original `enum` type – Hans Lub May 17 '16 at 10:06
3

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).

skyking
  • 13,817
  • 1
  • 35
  • 57