typedef는 작은 일 하나를 합니다. 이미 존재하는 타입에 다른 이름을 주는 것입니다. 새 타입이 만들어지지 않고, 메모리가 따로 잡히지 않으며, 값이 행동하는 방식도 전혀 바뀌지 않습니다. 이름 짓는 도구이고, 내장 타입 이름이 길어지는 C(unsigned long long, struct Employee, void (*)(int))에서 좋은 이름은 값이 큽니다.
기본 형태
문법은 앞에 typedef를 덧붙인 변수 선언처럼 읽힙니다. 변수 이름이 갈 자리에 새 타입 이름을 놓습니다.
unsigned long count; // count라는 변수를 선언한다
typedef unsigned long ulong; // ulong이라는 타입을 선언한다
그것이 규칙 전부이고, 이 페이지 뒤쪽의 이상해 보이는 것들을 포함해 여러분이 앞으로 읽게 될 모든 typedef를 설명해 줍니다.
Celsius/Fahrenheit 짝이 주지 않는 것을 보세요. 타입 안전성입니다. 둘 다 그냥 double이므로 섭씨를 기대하는 자리에 화씨 값을 넘겨도 기꺼이 컴파일됩니다. 이름은 사람을 위해 의도를 문서화할 뿐, 검사를 만들지 않습니다.
typedef struct 관용구
대부분의 C 프로그램에 typedef가 들어 있는 이유가 이것입니다. C에서 구조체의 타입 이름에는 키워드가 포함됩니다.
struct Point { int x; int y; };
struct Point p; /* 매번 "struct"라는 낱말 */
typedef가 그것을 줄입니다.
앞의 규칙으로 읽어 보세요. typedef를 떼면 struct Point {...} Point;, 즉 Point라는 변수의 선언입니다. typedef를 다시 붙이면 Point가 대신 타입 이름이 됩니다.
태그를 남길까 말까
태그를 아예 뺄 수도 있고, 그러면 typedef 이름만 가진 익명 구조체가 됩니다.
typedef struct {
int x;
int y;
} Point; /* "struct Point"는 존재하지 않는다 - "Point"만 있다 */
더 깔끔하고, 단순한 데이터 레코드에는 흔한 스타일입니다. 하지만 구조체가 자기 자신을 언급해야 하는 순간 실패합니다. typedef 이름은 자기 선언이 끝나기 전까지는 쓸 수 없기 때문입니다.
/* 컴파일되지 않는다: 자기 중괄호 안에서 "Node"는 아직 타입이 아니다. */
typedef struct {
int value;
Node *next;
} Node;
태그를 남기면 자기 참조가 동작합니다. 태그를 보는 즉시 struct Node를 쓸 수 있기 때문입니다.
실용적인 규칙은 이렇습니다. 태그를 남기세요. 낱말 하나가 들 뿐이고, typedef 이름과 일치하니 헷갈릴 것도 없으며, 자기 참조와 헤더의 전방 선언을 위한 문을 열어 둡니다. 저 연결된 노드가 무엇으로 자라는지는 구조체와 포인터를 보세요.
전방 선언과 불투명 타입
구조체를 가리키는 포인터는 구조체의 내용을 몰라도 크기가 알려져 있으므로, 헤더가 멤버를 드러내지 않고도 타입을 건네줄 수 있습니다.
/* stack.h */
typedef struct Stack Stack; /* 선언만, 정의는 아님 */
Stack *stack_create(void);
void stack_push(Stack *s, int value);
int stack_pop(Stack *s);
void stack_destroy(Stack *s);
완전한 struct Stack { ... };는 stack.c에 살고 바깥의 누구도 멤버를 건드릴 수 없습니다. 이 불투명 타입 패턴이 C 라이브러리가 캡슐화를 강제하는 방법이며, 표준 라이브러리의 FILE이 바로 그것입니다. fopen에서 FILE *를 받지만 그 안에 무엇이 있는지는 전혀 듣지 못합니다. 파일 처리를 보세요.
enum과 union의 typedef
같은 관용구가 다른 두 복합 종류에도 적용됩니다.
typedef가 없었다면 enum Color c;와 union Number n;이었을 것입니다. 그것들이 실제로 무엇을 하는지는 enum과 공용체를 보세요.
함수 포인터의 typedef
여기서 typedef는 편의를 넘어 거의 필수에 가까워집니다. 함수 포인터의 날것 문법은 읽기 어렵기로 유명합니다.
void (*handler)(int, const char *); /* 변수 */
void register_cb(void (*cb)(int, const char *)); /* 매개변수 */
타입에 한 번 이름을 붙이면 둘 다 읽을 만해집니다.
typedef를 떼는 규칙을 다시 적용해 보세요. int (*IntBinaryOp)(int, int);만 있으면 int 둘을 받아 int를 반환하는 함수를 가리키는 IntBinaryOp라는 변수의 선언입니다. typedef를 붙이면 그것이 타입의 이름이 됩니다. 자세한 내용은 함수 포인터에 있습니다.
typedef와 #define
단순한 별칭에 대해 서로 바꿔 쓸 수 있어 보이지만 그렇지 않습니다.
#define INT_PTR int *
typedef int *int_ptr;
INT_PTR a, b; /* 확장하면: int *a, b; -- b는 평범한 int! */
int_ptr c, d; /* c와 d 둘 다 int * */
#define은 컴파일러가 코드를 보기 전에 수행되는 맹목적인 텍스트 치환이고, typedef는 컴파일러가 이해하는 진짜 선언이라 목록의 모든 이름에 적용됩니다. 타입에는 typedef를 쓰고, #define은 상수와 매크로에 남겨 두세요.
typedef가 해가 될 때
typedef는 읽는 사람에게 필요한 정보를 감출 수도 있습니다. 가장 날카로운 예가 포인터를 감추는 것입니다.
typedef struct Node *NodeRef; /* 별표가 사라진다 */
NodeRef n = get_node();
NodeRef n을 보면 읽는 사람은 n이 NULL일 수 있는지, 해제해야 하는지, n과 그 사본이 같은 객체를 공유하는지, 멤버를 .으로 접근하는지 ->로 접근하는지 알 수 없습니다. 네 질문 모두 struct Node *n이었다면 즉시 답이 나옵니다.
표준 라이브러리도 동의합니다. 구조체를 typedef 하고(FILE) 별표는 사용하는 자리에 남겨 둡니다(FILE *fp). 여기서 합리적인 지침 둘이 나옵니다.
- 구조체, 공용체, enum, 함수 포인터는 typedef 하세요. 제거되는 잡음에는 정보가 담겨 있지 않습니다.
- 단지 짧게 하려고 포인터를 typedef 하지 마세요. 그리고 기본 타입을 위장하려고 typedef 하지 마세요(
typedef int Boolean;은 누군가 거기에7을 넣도록 부추깁니다. 대신 불리언에서 다루는<stdbool.h>의bool을 쓰세요).
<stdint.h>의 고정 폭 이름들(uint32_t, int64_t, size_t)은 모두 typedef이고, 이 기능을 옹호하는 가장 좋은 논거입니다. 자신이 무엇인지 정확히 말하고, 이식성이 있으며, 감추는 것이 없습니다.
자주 묻는 질문
C에서 typedef는 무슨 일을 하나요?
기존 타입에 두 번째 이름을 줍니다. typedef unsigned long ulong;은 그때부터 ulong과 unsigned long이 같은 타입이라는 뜻입니다. 새 타입도 새 저장 공간도 만들지 않고, 이미 있는 타입을 더 짧거나 더 설명적으로 쓰는 방법만 만듭니다.
C의 typedef struct 관용구란 무엇인가요?
typedef struct Point { int x; int y; } Point;는 구조체를 선언하면서 타입 이름을 Point로 짓는 일을 한 문장에 합니다. 그래서 struct Point p; 대신 Point p;라고 쓸 수 있습니다. 연결 리스트 노드처럼 구조체가 자기 자신을 참조해야 할 때는 태그(struct Point)를 남겨 두는 것이 중요합니다.
typedef와 #define의 차이는 무엇인가요?
typedef는 컴파일러가 처리하며 진짜 타입 별칭을 만듭니다. #define은 컴파일 전에 전처리기가 하는 텍스트 치환입니다. 이 차이는 포인터에서 뭅니다. #define PTR int*이면 PTR a, b;가 int* a, b;로 확장되어 a만 포인터입니다. typedef int *PTR;이면 둘 다 포인터입니다.
C에서 포인터 타입을 typedef 해야 하나요?
보통은 아닙니다. typedef struct Node *NodeRef;는 그 타입이 포인터라는 사실을 감추므로, 읽는 사람이 변수가 NULL일 수 있는지, 해제해야 하는지, ->가 필요한지 알 수 없습니다. 표준 라이브러리의 FILE *가 별표를 보이게 두는 이유가 정확히 그것입니다. 구조체를 typedef 하고, 그것을 가리키는 포인터는 하지 마세요.