簡體   English   中英

C中的Typesafe枚舉?

[英]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;

helloone是相同值的兩個名稱,即0 ,並且類型為int

hollaeins價值再次為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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM