[英]Typesafe enums in C?
如果我有多个enum
,例如:
enum Greetings{ hello, bye, how };
enum Testing { one, two, three };
如何强制使用正确的enum
? 举例来说,我不希望有人使用hello
时,他们应该使用one
更好的调试和可读性。
在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);
}
这不应该产生任何开销,并产生错误而不是警告:
$ 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’
请注意,我没有使用GNU扩展,也没有生成虚假警告。 只有错误。 另请注意,我使用的是一种与泥土一样古老的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.
这适用于任何支持C99复合文字的编译器。
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);
}
编译器输出:
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);
~~~~~~~~~~~ ^~~~~
如果用户要忽略编译器中的错误和警告,那么您无法帮助他们。
不幸的是, enum
是C类型系统中的一个弱点。 enum
类型的变量属于enum
类型,但是用enum
声明的常量是int
类型。
所以在你的例子中
enum Greetings{ hello, bye, how };
enum Testing { one, two, three };
enum Greetings const holla = hello;
enum Testing const eins = one;
hello
和one
是相同值的两个名称,即0
,并且类型为int
。
holla
和eins
价值再次为0
,但各有类型。
如果你想对“真正的”常量强制一些“官方”类型的安全性,那就是具有你想要的类型和值的实体,你必须使用一些更复杂的结构:
#define GREETING(VAL) ((enum Greetings){ 0 } = (VAL))
#define HELLO GREETING(hello)
GREETING
宏中的赋值确保结果为“rvalue”,因此无法对其进行修改,编译器将仅针对其类型和值进行修改。
这是你不想听到的答案。 在C中,你不能真的。 现在,如果你的C代码在C ++的“Clean C”子集中,你可以使用C ++编译器进行编译,以获得使用错误的enum / int值等的所有错误。
如果你还想确保有效范围,那么有一种技术可以带来指针取消引用的小开销,用于获取整数值 - 以及大量的样板输入。 它可能仍然有用,因为它可以帮助您编写范围检查代码,否则将需要它。
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;
}
示例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);
}
是的,很多要打字,但你得到完整的类型和范围安全。 没有其他编译单元可以实例化struct greetings
,因此您完全安全。
编辑2015-07-04:为了防止NULL,有两种可能性。 使用NULL作为默认值( #define Greetings_hello 0
而不是现在使用的指针)。 这非常方便,但默认枚举值的类型安全性,NULL可用于任何枚举。 或者声明它无效并在访问器方法中检查它,返回错误,或使用类似GCC __attribute__((nonnull()))
来在编译时捕获它,例如在greetings.h中:
const char *Greetings_str(const Greetings *g)
__attribute__((nonnull(1)));
您可以键入自己的枚举,然后声明这些类型的变量和函数参数。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.