简体   繁体   English

C中的Typesafe枚举?

[英]Typesafe enums in C?

If I have more than one enum , eg: 如果我有多个enum ,例如:

 enum Greetings{ hello, bye, how };

 enum Testing { one, two, three };

How can I enforce the usage of the correct enum ? 如何强制使用正确的enum For example, I don't want someone to use hello when they should be using one for better debugging and readability. 举例来说,我不希望有人使用hello时,他们应该使用one更好的调试和可读性。

In C, you can fake it with boilerplate code. 在C中,您可以使用样板代码伪造它。

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. 请注意,我没有使用GNU扩展,也没有生成虚假警告。 Only errors. 只有错误。 Also note that I'm using a version of GCC that's as old as dirt, 另请注意,我使用的是一种与泥土一样古老的GCC版本,

$ 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. 这适用于任何支持C99复合文字的编译器。

Clang produces the following warning, which is the best you can do (Although the user could upgrade the warning to an error). Clang产生以下警告,这是您可以做的最好的事情(虽然用户可以将警告升级为错误)。

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 . 不幸的是, enum是C类型系统中的一个弱点。 enum类型的变量属于enum类型,但是用enum声明的常量是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 . helloone是相同值的两个名称,即0 ,并且类型为int

holla and eins again have value 0 , but have their respective types. hollaeins价值再次为0 ,但各有类型。

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. GREETING宏中的赋值确保结果为“rvalue”,因此无法对其进行修改,编译器将仅针对其类型和值进行修改。

This is the answer you don't want to hear. 这是你不想听到的答案。 In C, you can't really. 在C中,你不能真的。 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. 现在,如果你的C代码在C ++的“Clean C”子集中,你可以使用C ++编译器进行编译,以获得使用错误的enum / int值等的所有错误。

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: 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: 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: 示例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. 没有其他编译单元可以实例化struct greetings ,因此您完全安全。


Edit 2015-07-04: For protecting against NULL, there are two possibilities. 编辑2015-07-04:为了防止NULL,有两种可能性。 Either use NULL for the default value ( #define Greetings_hello 0 instead of the pointer used now). 使用NULL作为默认值( #define Greetings_hello 0而不是现在使用的指针)。 This is very convenient, but drops type safety for default enum values, NULL can be used for any enum. 这非常方便,但默认枚举值的类型安全性,NULL可用于任何枚举。 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: 或者声明它无效并在访问器方法中检查它,返回错误,或使用类似GCC __attribute__((nonnull()))来在编译时捕获它,例如在greetings.h中:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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