如何创buildtypes安全的枚举?

在C中使用枚举来实现types安全是有问题的,因为它们实质上只是整数。 枚举常量实际上被定义为由标准的inttypes。

为了达到某种types的安全性,我使用这样的指针做技巧:

 typedef enum { BLUE, RED } color_t; void color_assign (color_t* var, color_t val) { *var = val; } 

因为指针比值更严格的types规则,所以这可以防止这样的代码:

 int x; color_assign(&x, BLUE); // compiler error 

但是这并不妨碍这样的代码:

 color_t color; color_assign(&color, 123); // garbage value 

这是因为枚举常量本质上只是一个int并可以隐式地分配给一个枚举variables。

有没有办法写这样一个函数或macroscolor_assign ,甚至可以实现完整的types安全枚举常量?

有一些技巧可以做到这一点。 特定

 typedef enum { BLUE, RED } color_t; 

然后定义一个不会被调用者使用的虚拟联合,但包含与枚举常量同名的成员:

 typedef union { color_t BLUE; color_t RED; } typesafe_color_t; 

这是可能的,因为枚举常量和成员/variables名称驻留在不同的名称空间中。

然后制作一些类似function的macros:

 #define c_assign(var, val) (var) = (typesafe_color_t){ .val = val }.val #define color_assign(var, val) _Generic((var), color_t: c_assign(var, val)) 

这些macros然后像这样调用:

 color_t color; color_assign(color, BLUE); 

说明:

  • C11 _Generic关键字确保枚举variables的types正确。 但是,这不能用于枚举常量BLUE因为它是inttypes的。
  • 因此,助手macrosc_assign创build虚拟联合的临时实例,其中使用指定的初始化程序语法将值BLUE分配给名为BLUE的联合成员。 如果不存在这样的成员,代码将不会被编译。
  • 然后将相应types的联合成员复制到枚举variables中。

我们其实不需要助手macros,我只是为了可读性而拆分expression式。 它的工作原理还不错

 #define color_assign(var, val) _Generic((var), \ color_t: (var) = (typesafe_color_t){ .val = val }.val ) 

例子:

 color_t color; color_assign(color, BLUE);// ok color_assign(color, RED); // ok color_assign(color, 0); // compiler error int x; color_assign(x, BLUE); // compiler error typedef enum { foo } bar; color_assign(color, foo); // compiler error color_assign(bar, BLUE); // compiler error 

编辑

显然,上面的内容并不妨碍调用者简单地inputcolor = garbage; 。 如果你想完全阻止使用这种枚举赋值的可能性,你可以把它放在一个结构体中,并使用“不透明types”的私有封装的标准过程:

color.h

 #include <stdlib.h> typedef enum { BLUE, RED } color_t; typedef union { color_t BLUE; color_t RED; } typesafe_color_t; typedef struct col_t col_t; // opaque type col_t* col_alloc (void); void col_free (col_t* col); void col_assign (col_t* col, color_t color); #define color_assign(var, val) \ _Generic( (var), \ col_t*: col_assign((var), (typesafe_color_t){ .val = val }.val) \ ) 

color.c

 #include "color.h" struct col_t { color_t color; }; col_t* col_alloc (void) { return malloc(sizeof(col_t)); // (needs proper error handling) } void col_free (col_t* col) { free(col); } void col_assign (col_t* col, color_t color) { col->color = color; } 

main.c中

 col_t* color; color = col_alloc(); color_assign(color, BLUE); col_free(color); 

最好的答案是相当不错的,但它的缺点是需要大量的C99和C11function才能编译,最重要的是,它使得赋值非常不自然:你必须使用color_assign()函数或macros来移动数据而不是标准=运算符。

(不可否认,这个问题明确地问到如何编写color_assign() ,但是如果你更广泛地看待这个问题,那么关于如何改变你的代码来使用某种forms的枚举常量来获得types安全性,我会考虑首先不需要color_assign()来获得types安全性是公平游戏的答案。)

指针是C对待types安全的几个形状之一,所以它们是解决这个问题的自然人选。 所以我会这样攻击它:而不是使用一个enum ,我会牺牲一点记忆,以便能够具有独特的,可预测的指针值,然后使用一些真正有趣的#define语句来构造我的“枚举”(是的,我知道macros会污染macros命名空间,但enum污染编译器的全局命名空间,所以我认为它接近偶数交易):

color.h

 typedef struct color_struct_t *color_t; struct color_struct_t { char dummy; }; extern struct color_struct_t color_dummy_array[]; #define UNIQUE_COLOR(value) \ (&color_dummy_array[value]) #define RED UNIQUE_COLOR(0) #define GREEN UNIQUE_COLOR(1) #define BLUE UNIQUE_COLOR(2) enum { MAX_COLOR_VALUE = 2 }; 

当然,这确实需要你在某处保留足够的内存,以确保没有别的东西可以接受这些指针值:

color.c

 #include "color.h" /* This never actually gets used, but we need to declare enough space in the * BSS so that the pointer values can be unique and not accidentally reused * by anything else. */ struct color_struct_t color_dummy_array[MAX_COLOR_VALUE + 1]; 

但从消费者的angular度来看,这一切都是隐藏的: color_t几乎是一个不透明的对象。 除了有效的color_t值和NULL之外,您不能分配任何内容:

user.c

 #include <stddef.h> #include "color.h" void foo(void) { color_t color = RED; /* OK */ color_t color = GREEN; /* OK */ color_t color = NULL; /* OK */ color_t color = 27; /* Error/warning */ } 

这在大多数情况下工作得很好,但是它在switch语句中没有工作的问题。 你不能switch一个指针(这是一个耻辱)。 但是,如果你愿意增加一个macros来切换,你可以得到一个“足够好”的东西:

color.h

 ... #define COLOR_NUMBER(c) \ ((c) - color_dummy_array) 

user.c

 ... void bar(color_t c) { switch (COLOR_NUMBER(c)) { case COLOR_NUMBER(RED): break; case COLOR_NUMBER(GREEN): break; case COLOR_NUMBER(BLUE): break; } } 

这是一个很好的解决scheme? 我不会称它为 ,因为它既浪费了一些内存,又污染了macros命名空间,并且不允许你使用enum来自动分配你的颜色值,但它另一种解决问题的方法,自然的用法,而不像顶级的答案,它一直工作回到C89。

最终,当你使用一个无效的枚举值时,你想要的是一个警告或错误。

正如你所说,C语言不能做到这一点。 然而,你可以很容易地使用静态分析工具来解决这个问题 – 铿锵是明显的自由的,但也有很多其他的。 无论语言是否是types安全的,静态分析都可以检测并报告问题。 通常情况下,静态分析工具会提示警告,而不会出现错误,但您可以轻松地让静态分析工具报告错误而不是警告,并更改您的生成文件或生成项目来处理此问题。

我们可以用一个struct来强制types安全:

 struct color { enum { THE_COLOR_BLUE, THE_COLOR_RED } value; }; const struct color BLUE = { THE_COLOR_BLUE }; const struct color RED = { THE_COLOR_RED }; 

由于color只是一个包装的整数,它可以通过值或指针传递,就像一个int 。 用这个color定义, color_assign(&val, 3); 无法编译:

错误:“color_assign”参数2的不兼容types

  color_assign(&val, 3); ^ 

完整(工作)的例子:

 struct color { enum { THE_COLOR_BLUE, THE_COLOR_RED } value; }; const struct color BLUE = { THE_COLOR_BLUE }; const struct color RED = { THE_COLOR_RED }; void color_assign (struct color* var, struct color val) { var->value = val.value; } const char* color_name(struct color val) { switch (val.value) { case THE_COLOR_BLUE: return "BLUE"; case THE_COLOR_RED: return "RED"; default: return "?"; } } int main(void) { struct color val; color_assign(&val, BLUE); printf("color name: %s\n", color_name(val)); // prints "BLUE" } 

在网上玩(演示) 。

这是我的解决scheme。 我命名空间的一切,所以我的枚举将被命名为:

 ns_a_e //ns = namespace; a=enum name; e = it's an enum 

其成员的范围如下:

 enum ns_a_e { ns_a_e__a, ns_a_e__b, ns_a_e__c, }; 

所以我想了一个可能修改上面的:

 typedef struct { enum { ns_a_e__a, ns_a_e__b, ns_a_e__c, } x; /*note: C doesn't scope the enumerators*/ } ns_a_e; 

然后我可以用类似的东西创build实例:

 #define NS_e(En,Val) (En){En##__##Val} 

或(伪)枚举特定的

 #define NS_a_e(Val) NS_e(ns_a_e,Val) 

这个简单的修改,我写NS_a_e(a)而不是我以前的ns_a_e__a使这些(伪)枚举完全types安全:

 ns_a_e x; x = NS_a_e(a); x = NS_a_e(b); //x = NS_a_e(foo); //ERROR //x = 1; //ERROR 

只要我的代码的其他地方尊重范围(= ns_a_e__*forms的符号属于ns_a_e

结构包装不会影响在x86-64上生成的程序集。

完整的例子(与-std=c90编译):

 #define NS_e(En,X) (En){En##__##X} ///////////////////////// typedef struct{ enum { ns_a_e__a, ns_a_e__b, ns_a_e__c, } x; } ns_a_e; #define NS_a_e(X) NS_e(ns_a_e, X) int main() { ns_a_e x; x = NS_a_e(a); x = NS_a_e(b); //x = NS_a_e(foo); //ERROR //x = 1; //ERROR }