Menu

C 언어 enum: 이름 붙은 상수, 자동 번호 매기기, enum을 문자열로

C에서 enum을 정의하는 방법 - 자동 번호 매기기, 명시적 값, switch에서 enum을 써서 컴파일러가 빠진 case를 잡게 하기, enum을 문자열로 변환하기, 그리고 읽기 좋게 유지하는 명명 규칙.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

맨숫자로 가득한 코드는 아무도 읽을 수 없는 코드입니다. if (state == 2)는 아무것도 말해 주지 않지만 if (state == STATE_RUNNING)은 모든 것을 말해 줍니다. enum은 작고 고정된 정수 집합에 제대로 된 이름을 붙이는 C의 방법이며, #define 더미와 달리 컴파일러가 아는 하나의 타입으로 묶여 옵니다.

enum 정의하기

선언은 태그에 이름을 붙이고 중괄호 안에 열거자(enumerator) 를 나열합니다.

바로 두 가지를 짚고 넘어갑시다. 첫째, 번호는 0에서 시작해 하나씩 증가하므로 RED는 0, GREEN은 1, BLUE는 2입니다. 둘째, 열거자 이름은 둘러싼 스코프 어디에서나 보입니다. Color.GREEN이 아니라 GREEN이라고 씁니다. 이들은 변수와 같은 이름 공간에 살기 때문에, C 코드는 관례적으로 열거자를 대문자로 외치거나 타입 이름을 접두사로 붙여(COLOR_RED) 충돌을 피합니다.

enum을 출력할 때는 %d를 씁니다. 열거 값은 정수 값이기 때문입니다.

명시적 값

아무 열거자에나 명시적 상수를 지정하면, 그 뒤의 것들은 거기서부터 이어집니다.

값이 고유하거나 순서대로일 필요는 없습니다. 두 열거자가 같은 숫자를 공유할 수 있고, 이는 별칭에 가끔 유용합니다(COLOR_DEFAULT = COLOR_BLACK). 다만 컴파일 타임 정수 상수여야 하므로, 변수로부터 계산할 수는 없습니다.

흔한 관용구는 이를 이용해 개수를 기록합니다.

enum Suit {
    CLUBS, DIAMONDS, HEARTS, SPADES,
    SUIT_COUNT           /* 자동으로 4 - 무늬를 추가해도 그대로 맞는다 */
};

SUIT_COUNT는 실제 무늬가 아니라 무늬의 개수이며, 자동 번호 매기기가 공짜로 관리해 줍니다. 반복문과 배열 크기가 이를 쓰면 절대 낡지 않습니다.

typedef enum

구조체와 마찬가지로 C에서 전체 타입 이름은 enum Color이고, typedef가 키워드를 덜어 줍니다.

열거자는 0부터 시작하는 평범한 정수이므로 배열 인덱스 역할도 겸합니다. dx[DIR_EAST]는 아무 변환 없이 동작합니다. 그것이 0부터 시작하는 enum의 가장 유용한 성질 하나입니다.

switch 속의 enum

enum을 switch와 짝지으면 그때부터 이 타입이 값을 합니다. -Wall로 컴파일하면 case를 빠뜨렸을 때 컴파일러가 알려 줍니다.

enum에 STATE_FAILED를 추가하면 GCC가 경고합니다. enumeration value 'STATE_FAILED' not handled in switch. 이 경고는 C가 가진 것 중 완전성 검사에 가장 가까운 것이며, 이런 switch를 default: 없이 쓰는 강력한 이유입니다. default는 경고를 침묵시켜서 새 상태가 처리되지 않은 채 빠져나가게 만듭니다. 대신 describe가 하듯 대비책을 switch 뒤에 두세요.

enum을 문자열로 변환하기

C는 런타임에 이름을 보관하지 않습니다. GREEN이 그저 1이므로 printf("%s", GREEN)은 동작할 수 없습니다. 두 가지 패턴이 이를 해결합니다.

컴파일러가 검사해 주는 switch 버전입니다.

그리고 더 짧지만 검사되지 않는 조회 테이블 버전입니다. 배열과 enum이 어긋나도 아무것도 경고하지 않습니다.

지정 초기화 인덱스([RED] = "RED")는 적어도 이름을 제 자리에 붙들어 두므로, enum 순서를 바꿔도 문자열이 조용히 뒤섞이지 않습니다. 경계 검사가 중요한 이유는 호출자가 enum 범위 밖의 값을 넘기는 것을 아무것도 막지 못하기 때문입니다 - 다음 절을 보세요.

enum은 빈틈없지 않습니다

enum 변수는 속을 들여다보면 정수이고, C는 그 범위를 단속하지 않습니다.

열거자에 대한 산술도 되고, 평범한 int와의 비교도 되고, 범위를 벗어난 캐스트도 받아들여집니다. 그러니 enum은 훌륭한 문서이자 좋은 컴파일러 힌트로 대하되, 보장으로 여기지는 마세요. 프로그램 바깥에서 들어오는 값 - 파일, 인자, 네트워크 메시지 - 은 switch 하기 전에 검증하세요.

크기와 저장

C17에서는 모든 열거자가 int에 들어가야 합니다. enum Huge { BIG = 3000000000 };은 제약 위반이라, gcc와 clang이 기본 모드에서 확장으로 받아 주더라도 엄격한 컴파일러(-pedantic-errors)는 거부합니다. 열거자 자체의 타입은 int이고, enum 타입의 크기는 구현 정의입니다(실제로는 int).

(C23은 이 제한을 풉니다. 열거자가 int 범위를 넘어도 되고, enum E : unsigned long { ... }처럼 바탕 타입을 직접 고를 수도 있습니다. 여러분의 툴체인이 C23을 대상으로 하기 전까지는 열거자를 int 안에 두세요.)

정확한 선택이 구현 정의이므로, enum을 바이너리 파일이나 네트워크 패킷에 그대로 써 놓고 다른 기계가 읽어 주기를 기대하지 마세요. 경계에서는 <stdint.h>uint8_tuint32_t 같은 고정 너비 타입으로 변환하세요.

enum과 #define 비교

둘 다 숫자에 이름을 줍니다. 대체로 enum이 낫습니다.

enum#define
처리 주체컴파일러전처리기
묶음관련 값들이 하나의 타입을 공유각 상수가 독립적
자동 번호 매기기안 됨, 손으로 번호를 매김
디버거에서 보임흔히 이름으로 보임안 됨, 숫자만
switch 완전성 경고안 됨
스코프블록 스코프를 따름텍스트 치환, #undef까지

상수가 정수가 아니거나(문자열, 실수), 조건부 컴파일처럼 전처리기 자신이 써야 할 때는 #define을 꺼내세요. 관련된 정수 상태, 플래그, 종류의 집합이라면 enum을 쓰세요.

명명 규칙

enum은 이름이 평범한 스코프에 놓이므로 충돌하기 쉽습니다. 큰 C 코드베이스를 온전하게 유지하는 규칙들입니다.

  • 각 열거자에 타입 접두사를 붙이세요: COLOR_RED, STATE_IDLE, HTTP_OK. 그래픽 프로그램의 접두사 없는 RED는 언젠가 다른 사람의 RED와 만납니다.
  • 열거자는 대문자로 외치세요. 다른 상수들의 모습과 맞춥니다.
  • 타입 이름은 단수형 PascalCase나 snake_case_t: Color, State, 또는 color_t를 프로젝트 전체에서 일관되게 쓰세요.
  • enum으로 반복하거나 인덱싱할 것이라면 마지막에 _COUNT 보초를 추가하세요.

자주 묻는 질문

C에서 enum은 어떻게 정의하나요?

enum Color { RED, GREEN, BLUE };는 세 개의 이름 붙은 상수를 값으로 갖는 타입을 정의합니다. 기본적으로 0부터 번호가 매겨지므로 RED는 0, GREEN은 1, BLUE는 2입니다. 변수는 enum Color c = GREEN;으로 선언하거나, typedef를 붙여 Color c라고만 쓸 수 있습니다.

C에서 enum에 특정 값을 지정할 수 있나요?

네. enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };처럼 씁니다. 일부만 지정하고 나머지는 이어지게 둘 수도 있습니다. 명시적 값이 없는 이름은 바로 앞의 값에서 이어지므로, enum { A = 10, B, C };는 B = 11, C = 12가 됩니다.

C에서 enum을 문자열로 어떻게 변환하나요?

C에는 내장된 방법이 없습니다. 열거자 이름은 런타임에 존재하지 않기 때문입니다. 보통은 값마다 문자열 리터럴을 반환하는 switch를 가진 작은 함수를 쓰거나, enum으로 인덱싱하는 문자열 배열을 씁니다. switch 버전이 더 안전한데, 새 열거자에 case가 없으면 -Wall이 경고해 주기 때문입니다.

C에서 enum의 크기는 얼마인가요?

구현 정의이지만, 실제로는 sizeof(int) - 대부분의 시스템에서 4바이트입니다. 컴파일러가 모든 열거자를 담을 수 있는 정수 타입을 고릅니다. 바이너리 파일이나 네트워크 패킷을 구성할 때 특정 크기를 가정하지 마세요. 그런 곳에서는 <stdint.h>의 고정 너비 타입을 쓰세요.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기