타입 세이프 에넘을 작성하는 방법
C에 enum이 있는 경우 기본적으로 정수이기 때문에 형식 안전을 달성하는 것은 문제가 있습니다. 열거 '형식'으로 정의되어 .int
기준으로는
약간의 안전성을 얻기 위해 나는 다음과 같은 포인터로 묘기를 부린다.
typedef enum
{
BLUE,
RED
} color_t;
void color_assign (color_t* var, color_t val)
{
*var = val;
}
포인터에는 값보다 엄격한 유형 규칙이 있으므로 다음과 같은 코드를 방지할 수 있습니다.
int x;
color_assign(&x, BLUE); // compiler error
그러나 다음과 같은 코드를 방지하지는 않습니다.
color_t color;
color_assign(&color, 123); // garbage value
'만'일 뿐이기 때문입니다.int
열거 변수에 암묵적으로 할당할 수 있습니다.
를 쓸 수 있는 요?color_assign
열거 상수에 대해서도 완전한 유형 안전성을 달성할 수 있습니까?
몇 가지 속임수로 이를 달성할 수 있습니다.정해진
typedef enum
{
BLUE,
RED
} color_t;
그런 다음 발신자가 사용하지 않지만 열거 상수와 이름이 같은 멤버를 포함하는 더미 결합을 정의합니다.
typedef union
{
color_t BLUE;
color_t RED;
} typesafe_color_t;
이는 열거 상수와 멤버/변수 이름이 서로 다른 네임스페이스에 존재하기 때문에 가능합니다.
그런 다음 다음과 같은 기능 매크로를 만듭니다.
#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))
이러한 매크로는 다음과 같이 불립니다.
color_t color;
color_assign(color, BLUE);
설명:
- ™ C11
_Generic
키워드를 지정하면 열거형 변수가 올바른 유형임을 확인할 수 있습니다. 열거 상수에서는 수 .BLUE
은 타입이기int
. - 매크로 " " " 입니다.
c_assign
유니언의 서 지정 값 "Designated Initializer" 를 합니다.여기서 지정된 이니셜라이저 구문을 사용하여 값을 할당합니다.BLUE
BLUE
이러한 멤버가 존재하지 않으면 코드가 컴파일되지 않습니다. - 그런 다음 대응하는 유형의 유니언 멤버가 열거형 변수로 복사됩니다.
사실 도우미 매크로는 필요 없습니다.가독성을 위해 식을 분할합니다.글을 쓰는 것 만큼이나 잘 된다.
#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
편집
, , 가 「」라고 간단하게 수 은 아닙니다.color = garbage;
이러한 열거형 할당을 사용할 가능성을 완전히 차단하려면 열거형을 구조체에 삽입하고 "opaque type"을 사용하여 개인 캡슐화의 표준 절차를 사용할 수 있습니다.
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;
}
메인
col_t* color;
color = col_alloc();
color_assign(color, BLUE);
col_free(color);
상위 답변은 매우 좋지만 컴파일하기 위해서는 많은 C99 및 C11 피처 세트가 필요하며, 게다가 할당이 매우 부자연스럽다는 단점도 있습니다.을 해color_assign()
이동 하기 위한 매크로=
환입니니다다
(부탁드립니다만, 이 질문은 어떻게 쓰는가에 대한 질문입니다.color_assign()
을 좀 더 넓게 상수를 코드를 것입니다 「」, 「」는 필요 없습니다. 그리고 나는 그것이 필요하지 않다고 생각합니다.color_assign()
정답을 위한 공정한 게임이 될 수 있도록 하는 것이 우선입니다.)
포인터는 C가 타입 세이프로 취급하는 몇 안 되는 도형이기 때문에 이 문제를 해결하기 위한 자연스러운 후보가 됩니다.저는 공격합니다. 것보다는요.enum
값을 수 약간의 그 후 값을 #define
enum네임스페이스를 시키는 것은 있습니다만, 「enum」은 매크로 네임스페이스를 오염시킵니다).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];
이 있습니다.color_t
거의 불투명한 물체입니다.한 것을 한 다른 수 없습니다.color_t
NULL: " " 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
위해 를 하나 더 과 같이 "충분히 수 있습니다.그러나 전환을 가능하게 하기 위해 매크로를 하나 더 추가할 의향이 있다면 다음과 같이 "충분히" 좋은 결과를 얻을 수 있습니다.
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;
}
}
이게 좋은 해결책인가요?메모리를 낭비하고 매크로 네임스페이스를 오염시키는 것, 그리고 그 때문에enum
색상 값을 자동으로 할당하는 것은 문제 해결의 또 다른 방법이지만, 상위 답변과는 달리 C89까지 작동합니다.
결국 잘못된 열거값을 사용할 때 경고 또는 오류가 발생합니다.
당신이 말한 것처럼 C언어는 이것을 할 수 없습니다.그러나 정적 분석 도구를 사용하여 이 문제를 쉽게 파악할 수 있습니다. Clang은 분명 무료이지만 그 밖에도 여러 가지가 있습니다.언어가 타입 세이프인지 아닌지에 관계없이 정적 분석을 통해 문제를 검출하고 보고할 수 있습니다.일반적으로 정적 분석 도구는 오류가 아닌 경고를 표시하지만 정적 분석 도구는 경고 대신 오류를 보고하고 이를 처리하기 위해 makefile 또는 빌드 프로젝트를 쉽게 변경할 수 있습니다.
「형식 은 「 안전」을 할 수 .struct
:
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
이며, 값할 수 있습니다. 예를 , is is 、 is is 、 is is 、 , is,, an an is is is is 。int
이로.color
,color_assign(&val, 3);
다음 중 하나:
오류: 'color_color'의 인수 2에 호환되지 않는 형식입니다.
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"
}
언급URL : https://stackoverflow.com/questions/43043246/how-to-create-type-safe-enums
'programing' 카테고리의 다른 글
복제/복사 상태가 빈 상태로 반환됩니다. (0) | 2022.08.11 |
---|---|
bootstrap-vue 테이블에 대한 세부 정보 표시 문제 (0) | 2022.08.11 |
vue.material에서의 vue.modial2 라우팅 (0) | 2022.08.11 |
메모리가 정렬되어 있는지 확인하는 방법 (0) | 2022.08.11 |
Base64 Java 인코딩 및 문자열 디코딩 (0) | 2022.08.11 |