728x90
반응형
선행처리(preprocess)란?
- 선행처리란 실행 파일을 생성하는 과정에서 소스 파일 내에 존재하는 선행처리 지시문을 처리하는 작업을 의미한다. 이러한 선행처리 작업은 컴파일하기 전 선행처리기(preprocessor)에 의해 먼저 처리된다.
- 선행처리기는 코드를 생성하는 것이 아니라, 컴파일러가 컴파일하기 좋도록 소스를 재구성해 주는 역할만을 한다.
선행처리문의 특징
- 선행처리문은 선행처리 문자(#)로 시작한다.
- 선행처리문은 코드 내에서 하나의 라인을 모두 차지하며, 선행처리문 뒤에 C언어 코드를 추가하여 같이 사용할 수 없다.
- 선행처리문은 다른 C언어의 명령문과는 달리 맨 뒤에 세미콜론(;)을 붙이지 않는다.
- 선행처리문은 소스 파일 어디에나 위치할 수 있지만, 선행처리문이 위치한 곳에서부터 파일의 끝까지만 영향을 미친다.
- 단, 주석은 같은 라인의 선행처리문 뒤에 위치할 수 있다.
선행처리 지시자(preprocessing directives)
- 선행처리 문자(#)로 시작하는 선행처리 지시자는 다음과 같다.

#include
- #include 선행처리 지시자는 외부에 선언된 함수나 상수 등을 사용하기 위해서 헤더 파일을 현재 파일에 포함할 때 사용한다.
- 선행처리기는 #include 지시자 뒤에 나오는 파일 이름을 보고 해당 파일을 찾아서 그 내용을 현재 파일에 포함해 준다.
- #include 선행처리 지시문에서 파일 이름을 표시하는 방법에는 ① #include과 ② #include "myStdio.h"이 있다.
- C언어에서 제공하는 표준 헤더 파일을 포함할 때에는 보통 꺾쇠괄호(<>)를 사용한다. 꺾쇠괄호를 사용하여 파일 이름을 표시하면, 선행처리기는 가장 먼저 표준 시스템 디렉터리에서 파일 이름에 해당하는 헤더 파일을 찾는다. 하지만 표준 시스템 디렉터리에서 파일 이름에 해당하는 헤더 파일을 찾지 못하면, 현재 작업 디렉터리도 검색한다.
- 사용자가 직접 작성한 헤더 파일을 포함할 때에는 보통 큰따옴표("")를 사용한다. 큰따옴표를 사용하여 파일 이름을 표시하면, 선행처리기는 가장 먼저 현재 작업 디렉터리에서 파일 이름에 해당하는 헤더 파일을 찾는다. 하지만 파일 이름에 해당하는 헤더 파일을 찾지 못하면, 표준 시스템 디렉터리도 검색한다.
- 따라서 결과적으로 이 두 방법에 큰 차이는 없지만, 많은 개발자가 이 기준에 맞춰 코드를 작성하고 있다.
#define
- #define 선행처리 지시자는 함수나 상수를 단순화해주는 매크로를 정의할 때 사용한다.
- 매크로는 함수나 상수에 이름을 붙임으로써, 해당 매크로가 무엇을 가리키고 있는지를 명확하게 나타내 준다. 따라서 코드의 가독성을 증가시키고, 코드를 훨씬 더 읽기 편하게 해준다.
#define 식별자 대체리스트
- 선행처리기는 #define 선행처리 지시문의 식별자(identifier)를 단순히 대체 리스트(replacement-list)로 치환해 주기만 한다. 이러한 과정을 매크로 확장(macro expansion)이라고 한다.
- #define 선행처리 지시문에서 식별자는 매크로(macro)라고 부르는 사용자가 미리 정의한 약어다. 이때 매크로 이름인 식별자는 C언어의 변수 이름 생성 규칙과 똑같은 생성 규칙을 따라서 작성해야 한다. 따라서 매크로 이름 중간에는 공백을 넣을 수 없지만, 실제값은 공백을 가질 수 있다. 또한, 매크로끼리 중첩하여 사용할 수도 있다. 단, 문자열에 포함된 매크로 이름에 대한 치환 작업은 이루어지지 않는다.
#include <stdio.h>
#define PI 3.14
int main(void) {
double radius = 12;
printf("원주율을 나타내는 PI의 값은 %.2f입니다.\n", PI);
printf("원의 면적은 %.2f * %.2f * %.2f = %.2f입니다.\n", PI, radius, radius, PI * radius * radius);
return 0;
}
// 원주율을 나타내는 PI의 값은 3.14입니다.
// 원의 면적은 3.14 * 12.00 * 12.00 = 452.16입니다.
- 위의 예제에서 PI라는 매크로는 3.14로 전부 단순 치환된다. 하지만 문자열에 포함된 PI라는 문자는 이러한 치환 작업에서 제외된다. 위처럼 값을 나타내는 매크로를 객체 같은 매크로(object-like macro)라고도 한다.
매크로 함수란?
- C언어에서는 #define 선행처리 지시문에 인수로 함수의 정의를 전달함으로써, 함수처럼 동작하는 매크로를 만들 수 있다. 이러한 매크로를 함수 같은 매크로(function-like macro) 또는 매크로 함수라고 한다.
#include <stdio.h>
#define SUB(X,Y) X-Y
#define PRT(X) printf("계산 결과는 %d입니다.\n", X)
int main(void) {
int result;
int num_01 = 15, num_02 = 7;
result = SUB(num_01, num_02);
PRT(result);
return 0;
}
// 계산 결과는 8입니다.
- 위의 예제는 SUB(X, Y)와 PRT(X)라는 매크로 함수를 정의하고 사용하는 예제다.
함수와 매크로 함수
- 매크로 함수는 일반 함수와는 달리 단순 치환만을 해주므로, 일반 함수와 완전히 똑같은 방식으로 동작하지는 않는다.
#include <stdio.h>
#define SQR(X) X*X
#define PRT(X) printf("계산 결과는 %d입니다.\n", X)
int main(void) {
int result;
int x = 5;
result = SQR(10);
PRT(result);
result = SQR(x);
PRT(result);
result = SQR(x+3);
PRT(result);
return 0;
}
// 계산 결과는 100입니다.
// 계산 결과는 25입니다.
// 계산 결과는 23입니다.
- 위 예제는 일반 함수와 매크로 함수와의 차이를 보여주는 예제다. 위의 예제에서 맨 마지막의 매크로 함수는 예상한 결과와는 전혀 다른 결괏값을 반환한다. 선행처리기는 매크로 정의에서 모든 X를 X+3으로 대체한다. 따라서 SQR(x+3)은 다음과 같이 대체되어 계산된다.
- x+3*x+3 = 5+3*5+3 = 5+15+3 = 23
- 예상한 결괏값은 8*8=64였지만 전혀 다른 결괏값이 반환되는 것이다. 일반 함수는 인수를 프로그램이 실행 중일 때 전달받지만, 매크로 함수는 인수를 컴파일 이전에 미리 치환하기 때문이다. 따라서 이와 같은 오류를 미리 방지하기 위해서는 다음 예제의 ①번 코드처럼 각 인수를 모두 괄호(())로 묶어줘야 한다.
#include <stdio.h>
① #define SQR(X) ((X)*(X)) // 매크로 함수는 이처럼 모든 인수를 괄호로 묶어줘야 함.
#define PRT(X) printf("계산 결과는 %d입니다.\n", X)
int main(void) {
int result;
int x = 5;
result = SQR(10);
PRT(result);
result = SQR(x);
PRT(result);
result = SQR(x+3);
PRT(result);
return 0;
}
// 계산 결과는 100입니다.
// 계산 결과는 25입니다.
// 계산 결과는 64입니다.
- 위의 예제는 앞선 예제를 수정하여 정상적인 결과를 반환하게 해주는 예제다. 이처럼 매크로 함수가 일반 함수와 같이 동작하기 위해서는 다음과 같은 사항에 주의하여 작성해야 한다. 매크로 함수는 얼핏 함수처럼 보이지만 일반 함수와는 전혀 다른 시간대에 이루어지는 다른 과정임을 명심해야 한다.
- 매크로 함수의 전체를 괄호(())로 감싸야 한다.
- 매크로 함수의 인수들도 각각 괄호로 감싸야 한다.
- 매크로 함수를 호출할 때에는 증감 연산자(++, --)나 복합 대입 연산자 등은 사용하지 않는 것이 좋다.
매크로 함수의 장점
- 매크로 함수는 단순 치환만을 해주므로, 인수의 타입을 신경 쓰지 않는다.
- 매크로 함수를 사용하면 여러 개의 명령문을 동시에 포함할 수 있다.
- 함수 호출에 의한 성능 저하가 일어나지 않으므로, 프로그램의 실행속도가 향상된다.
매크로 함수의 단점
- 원하는 결과를 얻는 정확한 매크로 함수의 구현은 어려우며, 따라서 디버깅 또한 매우 어렵다.
- 매크로 함수의 크기가 증가하면 증가할수록 사용되는 괄호 또한 매우 많아져서 가독성이 떨어진다.
- 따라서 매크로 함수는 크기가 큰 함수보다는 간단한 함수를 대체하는 데 사용하는 것이 좋다.
#과 ## 연산자
- #과 ## 연산자는 선행처리기 연산자로 #define 선행처리 지시문에서만 사용되는 연산자다.
- C언어에서 토큰(token)이란 컴파일러가 인식하는 최소 단위의 문자나 문자열을 의미한다. 이 두 연산자는 바로 이러한 토큰 단위의 연산에서 사용된다.
# 연산자
- # 연산자는 매크로 함수의 대체 리스트 안의 인수 앞에 사용하여, 토큰을 문자열로 변환시켜준다. 해당 토큰은 실인수로 치환되면서 양쪽에 위치한 큰따옴표("")를 포함해 그대로 문자열 상수로 변환된다.
- # 연산자를 사용하면 문자열 안에 매크로 함수로 전달된 인수를 포함시킬 수 있다.
#include <stdio.h>
#define SQR(X) printf(""#X"의 제곱은 %d입니다.\n", ((X)*(X)))
int main(void) {
int x = 5;
SQR(x);
SQR(3+4);
return 0;
}
// x의 제곱은 25입니다.
// 3+4의 제곱은 49입니다.
## 연산자
- ## 연산자는 두 개의 토큰을 하나의 토큰으로 결합해 주는 선행처리기 연산자다.
- 이 연산자는 함수 같은 매크로뿐만 아니라 객체 같은 매크로의 대체 리스트에도 사용할 수 있다.
- 이 연산자를 사용하면 변수나 함수의 이름을 프로그램의 런타임에 정의할 수 있다.
#include <stdio.h>
#define XN(n) x ## n
int main(void) {
int XN(1) = 10;
int XN(2) = 20;
printf("x1에 저장된 값은 %d입니다.\n", x1);
printf("x2에 저장된 값은 %d입니다.\n", x2);
return 0;
}
// x1에 저장된 값은 10입니다.
// x2에 저장된 값은 20입니다.
- 위 예제에서는 XN(n)이라는 매크로 함수를 사용하여 변수의 이름을 동적으로 작성하고 있다.
#include <stdio.h>
#define XN(n) x ## n
#define PRT_XN(n) printf("x"#n"에 저장된 값은 %d입니다.\n", x ## n)
int main(void) {
int XN(1) = 10;
int XN(2) = 20;
PRT_XN(1);
PRT_XN(2);
return 0;
}
// x1에 저장된 값은 10입니다.
// x2에 저장된 값은 20입니다.
- 위 예제는 # 연산자와 ## 연산자를 이용하여, 동적으로 작성한 변수의 이름에 접근하는 예제다.
미리 정의된 매크로(predefined macro)
- C언어에서는 컴파일러가 참고해야 할 정보를 알려주기 위해서 몇몇 매크로를 미리 정의하여 제공하고 있다.
- 미리 정의된 매크로는 #define 선행처리 지시자로 정의하지 않아도 사용할 수 있으나, 사용자가 재정의할 수는 없다.

#include <stdio.h>
int main(void) {
printf("선행처리가 수행된 날짜는 %s입니다.\n", __DATE__);
printf("선행처리가 수행된 시간은 %s입니다.\n", __TIME__);
printf("현재 소스 파일에서 처리중인 라인 번호는 %d입니다.\n", __LINE__);
printf("__STDC__ : %d\n", __STDC__);
printf("__STDC_HOSTED__ : %d\n", __STDC_HOSTED__);
return 0;
}
// 선행처리가 수행된 날짜는 Feb 15 2017입니다.
// 선행처리가 수행된 시간은 17:03:55입니다.
// 현재 소스 파일에서 처리중인 라인 번호는 7입니다.
// __STDC__ : 1
// __STDC_HOSTED__ : 1
#line
- #line 선행처리 지시자는 __LINE__ 매크로와 __FILE__ 매크로를 재정의할 수 있게 해준다.
- 라인 번호는 int형 타입으로, 파일명은 문자열로 전달된다.
- 이 선행처리 지시자는 사용자가 직접 사용하기보다는 주로 컴파일러가 오류 메시지를 위해 사용한다.
#error
- #error 선행처리 지시자는 지정한 오류 메시지를 출력하고, 컴파일 과정을 중단시킨다.
- 주로 조건부 컴파일 선행처리 지시자와 함께 사용하여 디버깅에 사용된다.
#undef
- #undef 선행처리 지시자는 #define 선행처리 지시지와 정확히 반대되는 동작을 수행한다. 즉, 이미 정의되어 있는 매크로를 취소하는 동작을 수행한다.
- 따라서 #define 선행처리 지시자에 의해 정의되는 매크로가 치환하는 범위는 #define 지시자가 정의된 위치부터 #undef 지시자에 의해 취소되는 위치나 파일의 끝까지가 된다. 단, 위에서 살펴본 미리 정의된 매크로는 사용자가 임의로 정의를 취소할 수 없다.
Reference
728x90
반응형
'Language > C' 카테고리의 다른 글
| [C] C언어 참조 - 바이트 저장 순서, 비트 단위 연산, 음수/실수의 표현 (0) | 2026.06.10 |
|---|---|
| [C] C언어 컴파일 - 헤더 파일, 분할 컴파일, 조건부 컴파일 (0) | 2026.06.09 |
| [C] C언어 입력과 출력 - 콘솔, 파일, 함수 (1) | 2026.06.08 |
| [C] C언어 구조체 - 개념, 포인터, 활용, 공용체와 열거체 (0) | 2026.06.08 |
| [C] C언어 문자와 문자열 - 입출력, 함수 (0) | 2026.06.07 |