구조체는 관련 값 여러 개를 한 덩어리로 묶었습니다. 오늘 다루는 세 가지는 결이 다릅니다. 열거형(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


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는 타입 이름을 짧게 다듬으며, 공용체는 공간을 아끼되 한 번에 하나만 담습니다. 공용체만은 넣은 멤버로 읽고 태그로 종류를 들고 다니는 습관을 지켜야 안전합니다.