전처리기
전처리기로 할 수 있는 일들
- 다른 파일을 인클루드
- 전처리기 지시문 #include을 사용
- 매크로를 다른 텍스트로 대체
- #define, #undef와 전처리기 연산자 #, ##를 사용
- 소스파일의 일부를 조건부로 컴파일
- 전처리기 지시문 #if, #ifdef, #ifndef, #else, #elif, #endif를 사용
- 일부로 오류를 발생시킴
- 전처리기 지시문 #error를 사용
매크로 대체 : #define
#define 식별자 대체_목록(선택)
- #define A (10)
- 전처리기가 소스 코드 뒤지다가 A가 보이면 모두 (10)으로 바꿔줌
- #define A
- 이것도 가능
- 하지만 바꿔줄 내용이 없음
- 그 대신 다른 전처리기 지시어로 A가 정의 돼 있는지 판단 가능
#define TRUE(1)
#define FALSE(0)
#define 식별자(매개변수) 대체_목록
- 심지어 함수처럼 쓰는 것도 가능
- 이 경우는 매크로 함수라고 함
매크로 대체 : #undef
#undef 식별자
- 이미 정의된 식별자를 없애는 것
- 해당 식별자로 정의된 텍스트 매크로가 없다면 이 지시문은 무시됨
매크로 대체 : 미리 정의되어 있는 #define
- 모든 C 구현이 정의하는 것들
- FILE : 현재 파일의 이름
- LINE : 현재 코드의 줄 번호를 정수형으로 표시
- 두 매트로 모두 오류 출력 시 자주 사용
fprintf(stderr, "internal error: %s, line %d.\n", __FILE__, __LINE__);
- (C95부터 지원) STDC_VERSION : 현재 컴파일에 사용 중인 C 표준
- 당연히 각 컴파일러가 자기 맘대로 정의하는 것들도 있음
조건부 컴팡리
- 조건이다 보니 if/else 문과 유사한 지시문들이 대거 포진
- 조건에 따라 특정 부분의 코드를 컴파일에 포함 또는 배제
#if 표현식
#ifdef 식별자 혹은 #if defined 식별자
#ifndef 식별자 혹은 #if !defined 식별자
#elif 표현식
#else
#endif
조건부 컴팡리 : 인클루드 가드
- 순환 헤더 인클루드, 즉, 헤더 꼬임을 방지
- 어떤 상수를 #define으로 정의
- 그 후 컴파일러에게 조건적으로 코드를 컴파일하라고 지시
#ifndef FOO_H #define FOO_H // 원래 헤더 파일 내용 #endif // FOO_H
어떤 식별자가 #define 되어있는지 판단
#ifndef NULL
#define NULL (0)
#endif
#if !defined(NULL)
#define NULL (0)
#endif
#if defined(NULL)
#undef NULL
#endif
#define NULL (0)
조건부 컴파일에서 주의할 점
#define A
#if defined(A) // 참
#define LENGTH (10)
#endif
#if A
#define LENGTH (10)
#endif
조건부 컴파일 : 버전 관리
새 기능을 추가 중일 때, 버전 관리용으로 사용할 수 있음
int spawn_monster(...)
{
get_monster_skin();
get_monster_stat();
#if defined(FILE_VERSION_2)
use_custom_skin(...);
#endif
calculate_spawn_location();
return TRUE;
}
- 어딘가에 #define FILE_VERSION_2라는 코드가 없으면 컴파일에 포함 안 됨
#elif와 #else를 사용해서 각 버전마다 필요한 작업을 할 수 있음
...
#if defined(FILE_VERSION_2)
use_custom_skin(...);
#elif defined(FILE_VERSION_3)
use_custom_voice(...);
#else
use_default_skin(...);
use_default_voice(...);
#endif
...
조건부 컴파일 : 주석 처리를 편하게
- #if 0와 #endif를 사용하면 보다 편하게 주석 처리가 가능
컴파일 오류 발생
#error 메세지
- 컴파일 도중에 강제로 오류를 발생시키는 매크로
- 메세지를 꼭 따옴표로 감쌀 필요는 없음
// version.h #define VERSION 10
// builder.h
#if VERSION != 11
#error "unsupported version"
#endif
## 컴파일 중에 매크로 정의하기
- 컴파일 도중에 -D 옵션으로 전달 가능
> clang -std=c89 -W -Wall -pedantic-errors -DA *.c
- #define A (1)과 똑같은 결과 (#define A가 아님)
- 직접 대체할 값 지정할 수 있음
> clang -std=c89 -W -Wall -pedantic-errors -DA=52 *.c
- #define A (52)와 똑같은 결과
## 배포용으로 컴파일하기 : -DNDEBUG
> clang -std=c89 -W -Wall -pedantic-errors -DNDEBUG *.c
- 배포(release) 모드로 실행파일을 컴파일하라고 알려주는 매크로
- NDEBUG: '디버그가 아니다'라는 뜻
- assert()가 사라짐
- 디버그 모드에서만 실행될 코드는 #if !defined(NDEBUG) 속에 넣을 것
- 이 대신 다음과 같은 매크로를 직접 정의해 사용하는 프로젝트 많음
- DEBUG : 디버그용 빌드
- RELEASE : 배포용 빌드
- 기타 : 필요에 따라 다양한 빌드를 지정
## 매크로 함수
- #define을 할 때 '대체 가능한 매개변수 목록'을 받음
```c
#define SQUARE(a) a * a
#define ADD(a, b) a + b
// main
int num1;
int num2;
int result;
num1 = 10;
num2 = 20;
result = ADD(num1, num2); // 30
result = SQUARE(num1); // 100
매크로 함수에서 흔히 하는 실수
#define SQUARE(a) a * a
#define ADD(a, b) a + b
// main
int num1;
int num2;
int result;
num1 = 10;
num2 = 20;
result = 10 * ADD(num1, num2); // 120
매크로 함수의 구현은 소괄호로 감싸준다
#define SQUARE(a) (a * a)
#define ADD(a, b) (a + b)
// main
int num1;
int num2;
int result;
num1 = 10;
num2 = 20;
result = 10 * ADD(num1, num2); // 300
베스트 프랙티스 : 매크로에 소괄호를 쓰자
#define ADD(a,b) a+b
#define ADD(a,b) (a+b)
매크로가 여러줄이면?
- \를 사용하면 매크로를 여러 줄로 나눌 수 있음
#define POW(n,p,i,r) r = 1; \ for(i = 0; i < p; ++i){ \ r *= n; \ }
매크로 함수의 활용: 어서트 재정의
- 어셈블리 코드를 이용한 나만의 어서트 매크로를 만들 수 있음
#define ASSERT(condition, msg) \ if(!(condition)){ \ fprintf(stderr, "%s(%s: %d)\n", msg, __FILE__, __LINE__); \ __asm {int 3} \ } \
// main
int month = 20;
ASSERT(month < 12, "invalid month number");
- 왜 어서트 매크로를 대신 사용할까?
- assert()는 실패 시 호출 스택의 위치가 assert() 함수 속
- __asm{int 3}는 실제로 어서트에 실패한 코드가 호출 스택의 현 위치
- 또한 사람이 읽기 편한 설명도 눈에 딱 보임(stderr 출력은 필수 아님)
- ASSERT(month < 12, "invalid month number");
- assert(month < 12);
- 단, int 3은 x86 어셈블리에서 프로그램 실행을 중지하는 인터럽트
- 플랫폼마다 사용하는 어셈블리 명령어가 달라짐
## 전처리기 명령어 : # 명령어
```c
#define 식별자(매개변수) 대체_목록
- 매개변수 자체를 문자열로 바꿔줌
- 매개변수를 쌍따옴표로 감싸는 것
#define str(s) #s
printf("%s\n", str(\n)); // new line
printf("%s\n", str("\n")); // "\n"
printf("%s\n", str(int main)); // int main
printf("%s\n", str("Hello World")); // "Hello World"
printf("%s\n", str(num1)); // num1
## 전처리기 명령어 : ## 명령어
```c
#define 식별자(매개변수) 대체_목록
- 대체 목록 안에 있는 두 단어를 합쳐서 새로운 텍스트로 바꿈
- 단어는 매개변수일 수도 아닐 수도 있음
- #와 달리 문자열 데이터를 만들어 주는 게 아님
#define print(n) printf("%d\n", g_id_##n)
int g_id_none = 0;
int g_id_teacher = 1;
int g_id_student = 2;
// main
print(number); // 컴파일 오류
print(none); // 컴파일 : 1 출력
print(student); // 컴파일 : 2 출력
## vs##
```c
#define combine1(a, b) (a#b)
#define combine2(a, b) (a##b)
// main
int student_id = 987654;
// 컴파일 오류 : student_"id"
printf("%d\n", combine1(student_, id));
// 컴파일 : student_id의 값인 987654를 출력
printf("%d\n", combine2(student_, id));
매크로 함수의 장점과 단점
- 장점
- 함수 호출이 아닌 곧바로 코드를 복붙하는 개념
- 함수 호출에 따른 과부하가 없음
- C에서 불편한 것들 중 일부는 매크로 꼼수로 해결 가능
- 단점
- 디버깅이 아주 어려움
- \를 사용해서 아무리 읽기 좋게 매크로 함수를 만들어도 중단점을 사용 불가
코드보기 : 전처리기를 이용한 튜플
#include <stdio.h>
// id(int), "name"(const char*), hp(int)
#define MONSTER_DATA \
MONSTER_ENTRY(0, "pope", 100) \
MONSTER_ENTRY(1, "big rat", 30) \
MONSTER_ENTRY(2, "mama", 255) \
MONSTER_ENTRY(3, "dragon", 300000) \
int main(void)
{
size_t i;
int ids[] = {
#define MONSTER_ENTRY(id, name, hp) id,
MONSTER_DATA
#undef MONSTER_ENTRY
};
const char* names[] = {
#define MONSTER_ENTRY(id, name, hp) name,
MONSTER_DATA
#undef MONSTER_ENTRY
};
int healths[] = {
#define MONSTER_ENTRY(id, name, hp) hp,
MONSTER_DATA
#undef MONSTER_ENTRY
};
for(i = 0; i < sizeof(ids) / sizeof(int); ++i)
{
printf("%3d %6d %s\n",
ids[i], healths[i], names[i]);
}
return 0;
}
getter 만들기
#include <stdio.h>
// (type, name)
#define MONSTER_STRUCT \
MONSTER_MEMBER(int, id) \
MONSTER_MEMBER(const char*, name) \
MONSTER_MEMBER(int, hp) \
typedef struct{
#define MONSTER_MEMBER(type, name) type name;
MONSTER_STRUCT
#undef MONSTER_MEMBER
} monster_t;
#define MONSTER_MEMBER(type, name) \
type get_mob_##name(const monster_t* mob) \
{ \
return mob->name; \
} \
MONSTER_STRUCT
#undef MONSTER_MEMBER
int main(void)
{
monster_t mob;
mob.id = 0;
mob.name = "Pope Mob";
mob.hp = 10001;
printf("%3d %6d %s\n",
get_mob_id(&mob),
get_mob_hp(&mob),
get_mob_name(&mob));
return 0;
}
'프로그래머 > C, C++' 카테고리의 다른 글
[포프 tv 복습] C99, C11 (0) | 2020.12.03 |
---|---|
[포프 tv 복습] 나만의 라이브러리 만들기, C99 (0) | 2020.12.01 |
[면접 대비] C를 사용한 해시 맵 구현 (0) | 2020.11.29 |
[면접 대비] C를 사용한 linked list 구현 (0) | 2020.11.29 |
[포프 tv 복습] C 자료구조 기초 (0) | 2020.11.29 |