简体   繁体   中英

Typesafe enums in C?

If I have more than one enum , eg:

 enum Greetings{ hello, bye, how };

 enum Testing { one, two, three };

How can I enforce the usage of the correct enum ? For example, I don't want someone to use hello when they should be using one for better debugging and readability.

In C, you can fake it with boilerplate code.

typedef enum { HELLO_E, GOODBYE_E } greetings_t;
struct greetings { greetings_t greetings; };
#define HELLO ((struct greetings){HELLO_E})
#define GOODBYE ((struct greetings){GOODBYE_E})

typedef enum { ONE_E, TWO_E } number_t;
struct number { number_t number; };
#define ONE ((struct number){ONE_E})
#define TWO ((struct number){TWO_E})

void takes_greeting(struct greetings g);
void takes_number(struct number n);

void test()
{
    takes_greeting(HELLO);
    takes_number(ONE);

    takes_greeting(TWO);
    takes_number(GOODBYE);
}

This should not incur any overhead, and produces errors instead of warnings:

$ gcc -c -std=c99 -Wall -Wextra test2.c
test2.c: In function ‘test’:
test2.c:19: error: incompatible type for argument 1 of ‘takes_greeting’
test2.c:20: error: incompatible type for argument 1 of ‘takes_number’

Notice that I'm not using GNU extensions, and no spurious warnings are generated. Only errors. Also note that I'm using a version of GCC that's as old as dirt,

$ gcc --version
powerpc-apple-darwin9-gcc-4.0.1 (GCC) 4.0.1 (Apple Inc. build 5493)
Copyright (C) 2005 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

This should work with any compiler with support for C99's compound literals.

Clang produces the following warning, which is the best you can do (Although the user could upgrade the warning to an error).

enum Greetings { hello, bye, how };
enum Count { one, two, three };

void takes_greeting(enum Greetings x) {}
void takes_count(enum Count x) {}

int main() {
    takes_greeting(one);
    takes_count(hello);
}

Compiler output:

cc     foo.c   -o foo
foo.c:8:17: warning: implicit conversion from enumeration type 'enum Count' to different enumeration type 'enum Greetings' [-Wenum-conversion]
        takes_greeting(one);
        ~~~~~~~~~~~~~~ ^~~
foo.c:9:14: warning: implicit conversion from enumeration type 'enum Greetings' to different enumeration type 'enum Count' [-Wenum-conversion]
        takes_count(hello);
        ~~~~~~~~~~~ ^~~~~

If users are going to ignore errors and warnings from the compiler, then there's not much you can do to help them.

Unfortunately enum are a weak point in the type system of C. Variables of an enum type are of that enum type, but the constants that you declare with the enum are of type int .

So in your example

enum Greetings{ hello, bye, how };
enum Testing { one, two, three };

enum Greetings const holla = hello;
enum Testing const eins = one;

hello and one are two names for the same value, namely 0 , and with the same type int .

holla and eins again have value 0 , but have their respective types.

If you want to force some "official" type safety for "real" constants, that is entities that have the type and value that you want, you'd have to use some more involved constructs:

#define GREETING(VAL) ((enum Greetings){ 0 } = (VAL))
#define HELLO GREETING(hello)

The assignment in the GREETING macro ensures that the result is an "rvalue" so it can't be modified and will be taken by the compiler just for its type and value.

This is the answer you don't want to hear. In C, you can't really. Now if your C code were in the "Clean C" subset of C++, you could compile with the C++ compiler to get all the errors of using the wrong enum/int values, etc.

If you also want to ensure the valid range, there's a technique that comes with the little overhead of a pointer dereference for getting the integer value -- and a lot of boilerplate typing. It might still be useful because it leverages you from writing range-checking code where it would be otherwise necessary.

greetings.h:

#ifndef GREETINGS_H
#define GREETINGS_H

struct greetings;
typedef struct greetings Greetings;

extern const Greetings * const Greetings_hello;
extern const Greetings * const Greetings_bye;
extern const Greetings * const Greetings_how;

const char *Greetings_str(const Greetings *g);
int Greetings_int(const Greetings *g);

#endif

greetings.c:

#include "greetings.h"

struct greetings {
    const int val;
};

static const Greetings hello = { 0 };
static const Greetings bye = { 1 };
static const Greetings how = { 2 };

const Greetings * const Greetings_hello = &hello;
const Greetings * const Greetings_bye = &bye;
const Greetings * const Greetings_how = &how;

static const char * const Greetings_names[] = {
    "hello",
    "bye",
    "how"
};

const char *
Greetings_str(const Greetings *g)
{
    return Greetings_names[g->val];
}

int
Greetings_int(const Greetings *g)
{
    return g->val;
}

example main.c:

#include <stdio.h>
#include "greetings.h"

void
printTest(const Greetings *greeting)
{
    if (greeting == Greetings_how) return;
    puts(Greetings_str(greeting));
}

int
main()
{
    const Greetings *g = Greetings_hello;
    printTest(g);
}

Yes, a LOT to type, but you get full type and range safety. No other compilation unit can ever instantiate a struct greetings , so you are completely safe.


Edit 2015-07-04: For protecting against NULL, there are two possibilities. Either use NULL for the default value ( #define Greetings_hello 0 instead of the pointer used now). This is very convenient, but drops type safety for default enum values, NULL can be used for any enum. Or declare it invalid and either check for it in the accessor methods, returning an error, or use something like GCCs __attribute__((nonnull())) to catch it at compile time, eg in greetings.h:

const char *Greetings_str(const Greetings *g)
        __attribute__((nonnull(1)));

您可以键入自己的枚举,然后声明这些类型的变量和函数参数。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM