구조체는 관련 값 여러 개를 한 덩어리로 묶었습니다. 오늘 다루는 세 가지는 결이 다릅니다. 열거형(enum, 이름 붙은 정수)은 숫자에 이름을 달고, 공용체(union, 메모리 공유 타입)는 하나의 공간을 여러 이름으로 나눠 쓰며, typedef(타입 별칭)는 긴 타입 이름에 짧은 별명을 붙입니다. 셋 다 코드를 읽기 쉽게 하는 도구지만, 공용체는 원리를 모르면 엉뚱한 값을 읽는 함정이 있습니다.
21.1 이름 없는 숫자와 열거형
상태나 종류를 정수로만 표현하면 코드가 금방 읽기 어려워집니다. today가 2일 때 그게 수요일인지 주차장 2층인지 코드만 봐서는 알 수 없습니다. 열거형은 이런 숫자에 이름을 붙여, 뜻이 드러나게 합니다.
#include <stdio.h>
enum Day { MON, TUE, WED, THU, FRI }; // MON=0부터 1씩
int main(void) {
enum Day today = WED;
printf("코드값: %d\n", today);
if (today == WED) printf("수요일입니다.\n");
return 0;
}
출력:
코드값: 2
수요일입니다.
enum은 첫 이름에 0을 주고 그다음부터 1씩 늘립니다. 그래서 MON은 0, WED는 2입니다. 이제 코드 본문에는 today == WED처럼 의미가 보이는 비교만 남습니다.
21.2 열거형 값을 직접 정하기
기본값이 마음에 안 들면 원하는 숫자를 직접 지정합니다. 서버 응답 코드처럼 이미 정해진 숫자에 이름을 묶을 때 특히 유용합니다.
enum HttpStatus { OK = 200, NOT_FOUND = 404, SERVER_ERROR = 500 };
enum HttpStatus s = NOT_FOUND;
printf("응답 코드: %d\n", s);
출력:
응답 코드: 404
일부만 지정하면, 그다음 이름은 지정한 값에서 다시 1씩 이어집니다.
enum Level { LOW = 1, MID, HIGH }; // MID=2, HIGH=3
printf("%d %d %d\n", LOW, MID, HIGH);
출력:
1 2 3
함정. 값을 겹치게 지정하면 서로 다른 이름이 같은 숫자가 됩니다. 컴파일러는 막지 않지만, 그 두 이름을 한 switch의 case로 함께 쓰면 중복 case 컴파일 오류가 납니다.
21.3 열거형과 switch의 조합
상태를 이름으로 두면 분기 코드가 눈에 잘 들어옵니다. switch와 함께 쓰는 것이 전형적입니다.
enum State { READY, RUNNING, DONE };
enum State s = RUNNING;
switch (s) {
case READY: printf("대기 중\n"); break;
case RUNNING: printf("진행 중\n"); break;
case DONE: printf("완료\n"); break;
}
출력:
진행 중
여기에 gcc의 -Wall 옵션을 켜면 switch에서 빠뜨린 case를 경고해 줍니다. 상태를 하나 늘렸는데 처리를 깜빡한 곳을 컴파일 단계에서 잡아 주니, 상태 관리에 안전합니다.
21.4 typedef, 타입에 붙이는 별칭
typedef는 이미 있는 타입에 새 이름을 답니다. 새 타입을 만드는 게 아니라, 부르는 이름 하나를 더 두는 것뿐입니다.
typedef unsigned long ulong;
ulong big = 4000000000UL;
printf("%lu\n", big);
출력:
4000000000
unsigned long을 ulong으로 줄이면 선언이 짧아집니다. 별칭은 특히 구조체와 만나면 효과가 큽니다.
21.5 typedef와 구조체, 열거형
C에서는 구조체 변수를 만들 때마다 struct Student a; 처럼 struct를 붙여야 합니다. typedef로 별칭을 달면 이 struct를 뗄 수 있습니다.
typedef struct {
int x;
int y;
} Point; // 이제 Point 만으로 사용
Point p = {3, 4};
printf("(%d, %d)\n", p.x, p.y);
출력:
(3, 4)
열거형도 같은 방식으로 짧게 부를 수 있습니다.
typedef enum { RED, GREEN, BLUE } Color;
Color c = GREEN;
printf("색 코드: %d\n", c);
출력:
색 코드: 1
21.6 공용체, 하나의 공간을 나눠 쓰기
구조체는 멤버마다 각자의 공간을 갖습니다. 공용체는 정반대로, 모든 멤버가 같은 메모리를 겹쳐 씁니다. 그래서 한 순간에는 멤버 하나만 담깁니다.
union Value {
int i;
float f;
};
union Value v;
v.i = 65;
printf("정수로: %d\n", v.i);
v.f = 3.14f; // 같은 공간을 실수로 덮어씀
printf("실수로: %.2f\n", v.f);
출력:
정수로: 65
실수로: 3.14
v.i에 넣었다가 v.f에 넣으면, 두 멤버가 한 공간을 공유하므로 같은 자리를 실수 표현이 덮어씁니다.
21.7 공용체의 크기와 한 번에 하나만
공용체의 크기는 가장 큰 멤버에 맞춰집니다. 겹쳐 놓을 공간이 하나면 충분하기 때문입니다.
union Mix { char c; int i; double d; };
printf("크기: %zu\n", sizeof(union Mix));
출력:
크기: 8
char 1, int 4, double 8 중 가장 큰 8바이트에 맞춰졌습니다. 문제는 넣은 멤버와 다른 멤버로 읽을 때 생깁니다.
union Value v;
v.f = 3.14f;
printf("%d\n", v.i); // 실수를 정수로 읽음
출력(환경마다 다름):
1078523331
함정. 공용체는 한 번에 하나의 멤버만 유효합니다. f에 넣고 i로 읽으면 실수의 비트를 정수로 해석한 쓰레기 값이 나옵니다. 값이 환경마다 달라 재현조차 어렵습니다. 그래서 지금 어떤 멤버가 유효한지를 프로그래머가 따로 기억하고 있어야 합니다.
![[C 21] 공용체, 열거형, typedef](https://img.thenullpage.com/posts/6556/6556_1_f854fc.webp)
21.8 태그를 붙인 공용체
어떤 멤버가 유효한지 기억하는 표준 방법이 태그입니다. 공용체 옆에 종류를 나타내는 열거형을 두고 둘을 구조체로 묶습니다. 세 가지를 한 번에 쓰는 실전 형태입니다.
typedef enum { INT_KIND, FLOAT_KIND } Kind;
typedef struct {
Kind kind; // 지금 유효한 멤버 표시
union { int i; float f; } data;
} Box;
void print_box(Box b) {
if (b.kind == INT_KIND)
printf("정수 %d\n", b.data.i);
else
printf("실수 %.2f\n", b.data.f);
}
int main(void) {
Box a = { INT_KIND, { .i = 7 } };
Box b = { FLOAT_KIND, { .f = 1.5f } };
print_box(a);
print_box(b);
return 0;
}
출력:
정수 7
실수 1.50
kind를 먼저 확인하고 그에 맞는 멤버만 읽으니, 앞서 본 함정을 원천에서 막습니다. enum은 종류에 이름을 주고, union은 정수와 실수 자리를 공유해 공간을 아끼며, typedef는 struct와 enum을 짧게 부르게 합니다. 타입 하나로 정수도 실수도 담는 이 구조는 설정 값 처리에 흔히 등장합니다.
세 도구는 목적이 뚜렷합니다. 열거형은 숫자에 이름을 붙여 의도를 드러내고, typedef는 타입 이름을 짧게 다듬으며, 공용체는 공간을 아끼되 한 번에 하나만 담습니다. 공용체만은 넣은 멤버로 읽고 태그로 종류를 들고 다니는 습관을 지켜야 안전합니다.