
C 언어 enum은 숫자를 보기 좋게 바꾸는 장식이 아닙니다. 매직 넘버를 의미 있는 이름으로 바꿔서, switch와 상태 코드를 읽을 때 값보다 뜻이 먼저 보이게 만드는 도구에 가깝습니다.
이번 글에서는 enum 문법을 길게 늘어놓기보다, 왜 named constant와 상태 묶음에 잘 맞는지, switch 가독성과 디버깅에서 어떤 차이를 만드는지, 그리고 초보자가 어디서 자주 오해하는지까지 쉬운 예시로 정리하겠습니다.
먼저 보기
- enum은 이름이 붙은 정수 상수 묶음이다
- 매직 넘버 대신 의미 있는 이름을 남긴다
- 관련된 상태를 한 군데에 모아 관리하기 쉽다
- switch 문이 숫자 비교가 아니라 상태 분기로 읽히기 시작한다
int state = 1;
if (state == 0) {
puts("대기");
} else if (state == 1) {
puts("실행 중");
} else if (state == 2) {
puts("종료");
}이 코드는 동작합니다. 하지만 1이 무슨 뜻인지 코드를 다시 번역해야 한다는 점이 문제입니다.
enum State {
STATE_IDLE,
STATE_RUNNING,
STATE_DONE
};
enum State state = STATE_RUNNING;
if (state == STATE_IDLE) {
puts("대기");
} else if (state == STATE_RUNNING) {
puts("실행 중");
} else if (state == STATE_DONE) {
puts("종료");
}이제 숫자를 외우지 않아도 됩니다. 값보다 의미가 먼저 보인다는 점이 enum의 가장 큰 장점입니다.
enum이 필요한 이유
이름이 남는다
숫자는 저장되지만, 의도는 남지 않습니다. 반면 enum은 상수 이름 자체가 문서 역할을 합니다. 0은 해석이 필요하지만 STATE_IDLE은 읽는 순간 뜻이 보입니다.
묶음이 생긴다
상태값이 여기저기 흩어져 있으면 수정하기 어렵습니다. enum은 관련된 값을 한 덩어리로 모아 줍니다.
enum HttpStatus {
HTTP_OK = 200,
HTTP_NOT_FOUND = 404,
HTTP_SERVER_ERROR = 500
};실수 탐지가 쉬워진다
enum이 모든 잘못을 막아 주는 것은 아닙니다. 그래도 enum State 같은 문맥이 있으면 state = 7 같은 값이 훨씬 더 수상하게 보입니다. 사람이 읽으며 이상 신호를 빨리 잡기 쉬워집니다.
C 언어 enum과 switch
C 언어 enum이 가장 체감되는 지점 중 하나는 switch입니다. 숫자로 분기하면 case가 값 비교로만 보이지만, enum으로 분기하면 상태 분기로 읽힙니다.
switch (state) {
case 0:
puts("대기");
break;
case 1:
puts("실행 중");
break;
case 2:
puts("종료");
break;
default:
puts("알 수 없음");
break;
}enum State {
STATE_IDLE,
STATE_RUNNING,
STATE_DONE
};
switch (state) {
case STATE_IDLE:
puts("대기");
break;
case STATE_RUNNING:
puts("실행 중");
break;
case STATE_DONE:
puts("종료");
break;
default:
puts("알 수 없음");
break;
}case 1보다 case STATE_RUNNING이 더 읽기 쉬운 이유는 숫자 대신 상태 이름이 바로 보이기 때문입니다.
디버깅
디버깅은 결국 값을 읽는 일입니다. 그런데 state=2만 보이면 그 값이 무엇을 뜻하는지 다시 찾아야 합니다. enum을 써 두면 적어도 어떤 값들이 허용되는지 한 군데서 볼 수 있고, switch 분기와 상태표를 이름 기준으로 따라가기 쉬워집니다.
물론 C에서 enum 값이 자동으로 문자열처럼 출력되는 것은 아닙니다. printf로 찍으면 보통 정수로 보입니다. 그래서 실전에서는 문자열 변환 함수를 함께 두면 더 읽기 좋아집니다.
const char *state_to_string(enum State state)
{
switch (state) {
case STATE_IDLE:
return "IDLE";
case STATE_RUNNING:
return "RUNNING";
case STATE_DONE:
return "DONE";
default:
return "UNKNOWN";
}
}printf("state=%s\n", state_to_string(state));상태 묶음
enum은 한 주제에 속한 값들을 묶을 때 특히 좋습니다. 파일 처리 흐름처럼 단계가 정해진 경우가 대표적입니다.
enum FileStep {
FILESTEP_OPEN,
FILESTEP_READ,
FILESTEP_PARSE,
FILESTEP_CLOSE
};이렇게 써 두면 이 값들이 서로 관련된 순서라는 점이 바로 드러납니다. #define으로 흩어 두는 것보다 상수 집합의 모양이 더 또렷하게 남습니다.
자주 하는 오해
강한 타입처럼 생각한다
C의 enum은 이름이 붙은 정수 상수 집합에 가깝습니다. 타입 문맥에 참여하지만, 다른 언어처럼 잘못된 숫자 대입을 항상 완벽하게 막아 주는 것은 아닙니다.
값을 꼭 직접 줘야 한다고 생각한다
값을 생략하면 첫 항목은 0, 이후는 1씩 증가합니다. 중간 값만 직접 지정할 수도 있습니다.
enum ErrorCode {
ERR_OK = 0,
ERR_IO = 10,
ERR_TIMEOUT,
ERR_PERMISSION
};이 경우 ERR_TIMEOUT은 11, ERR_PERMISSION은 12가 됩니다.
enum과 #define을 같게 본다
둘 다 이름을 만들 수는 있습니다. 하지만 enum은 관련 상수를 한 묶음으로 선언한다는 점이 다릅니다. 그래서 switch, 상태 관리, 유지보수 흐름에서 더 읽기 좋은 경우가 많습니다.
enum 이름을 그냥 쓸 수 있다고 생각한다
기본적으로는 enum Color처럼 써야 합니다. Color만 쓰고 싶다면 typedef를 붙여 별칭을 만들 수 있습니다. 선언 읽기 감각은 C 언어 struct와 typedef 차이 글과 함께 보면 더 잘 잡힙니다.
enum Color {
COLOR_RED,
COLOR_GREEN,
COLOR_BLUE
};
typedef enum Color Color;
Color color = COLOR_RED;언제 쓰면 좋을까
- 상태값이 몇 가지로 고정돼 있다
- 메뉴 번호나 모드 값이 있다
- switch로 분기할 일이 많다
- 같은 종류의 상수가 반복된다
- 숫자 대신 의미를 남기고 싶다
포인터 감각을 같이 정리하고 싶다면 C 언어 포인터와 배열 차이 글을 함께 보면 좋습니다. 기본 개념은 cppreference의 enum 문서와 switch 문서를 기준으로 확인했습니다.
정리
C 언어 enum은 숫자를 꾸미는 문법이 아니라, 관련된 정수 상수에 이름과 문맥을 붙이는 도구입니다. 그래서 매직 넘버를 줄이고, switch를 읽기 쉽게 만들고, 상태값을 한 그룹으로 관리하는 데 특히 유용합니다.
결국 핵심은 성능보다 의미입니다. 같은 숫자라도 1보다 STATE_RUNNING이 더 많은 정보를 남긴다면, 그 코드는 이미 더 읽기 쉬워진 것입니다.