프리 프로세서는 컴파일에 앞서 소스 코드를 지시된 내용에 따라 미리 처리하는 프로세서이다. 이 프리 프로세서에 지시를 주는 제어문으로서 #로 시작하는 몇 가지 문이 있지만, 본 내용에서는 가장 대표적인 #include와 #define 에 관해서 설명한다. 또 인수를 포함하는 매크로에 관해서도 설명한다.
1. 프리 프로세서
프리 프로세서란 컴파일에 앞서서 소스 코드를 지정된 내용에 따라 전환된 값으로 처리하는 과정이다. 이렇게 바꾸어 쓰는 내용은 앞에서도 설명하였지만, #include나 #define과 같은 #으로 시작되는 문이다. 이러한 것들을 프리 프로세서(pre-processor) 제어문이라고 한다.
Turbo C의 프리 프로세서 제어문은 다음과 같다.
#define #ifdef
#elif #ifdef
#else #include
#endif #line
#error #pragma
#if #undef
2. #include
#include 는 이 위치에 지정한 파일을 포함시키는 것으로
#include < 파일명>
또는
# include "파일명"
라고 표시한다.
<파일명>과 "파일명"과의 차이는 파일 탐색 과정이 다르다. <파일명>을 지정하면, 표준 디렉토리만을 찾는다. 또 "파일명"을 지정하게 되면 처음에는 현재 사용 중인 디렉토리를 찾고, 없으면 표준 디렉토리를 찾는다.
통상 들어가는 파일은 #define, 외부 변수, 함수의 형 선언이 들어간다. 이것을 헤더파일이라고 부르지만, C의 문법에 따르고 있으면 무엇이던지 가능하다.
/* file 'mytype.h' */
#define BOOL int
#define TRUE 1
#define FALSE 0
/* file 'test.c' */
#include "mytype.h"
BOOL whitespace(c)
char c;
{
if ( c == ' ' || c == '\n' || c == '\t' )
return TRUE;
else return FALSE;
}
확인 문제 1
/* #define과 #include 지시문을 사용한 예제 프로그램 */
#include <stdio.h>
#include "condition.txt"
main( )
{
while(printf("Input two integers: "), scanf("%d %d", &a, &b) == TRUE)
{
CONDITION(a, b);
if(a < b ) WRITE_1(a, b);
else WRITE_2(a, b);
}
}
3. #define
(1) 단순한 치환
#define을 사용한 단순한 치환은 다음과 같다.
#define 기호상수 문자열
프리 프로세서는 #define으로 정의되어 있는 기호 상수를 소스 파일에서부터 찾아 그것을 모두 문자열로 치환한다. 기호 상수와 문자열 사이는 하나 이상의 공간이나 탭으로 구분한다. 또 소스 파일 내의 기호 상수는 일반 변수와 혼동되지 않도록 다음과 같이 대문자로 쓰는 경우가 많다. #define은 프로그램 본체 중에서 특정한 값을 치환하지 않고 처리하는데 편리하다.
#define PI 3.14
#define MAX 100
#define YES 1
#define NO 0
예를 들어, 다음과 같은 성적 처리 프로그램을 생각하자.
#define STUDENT 40
main( )
{
for(k = 1; k <= STUDENT; k++)
for(j = 1 ;j < STUDENT; j++)
만약에 STUDENT를 사용하지 않고 프로그램 몸체에 직접 40을 넣으면 학생수의 변동에 따라 40을 바꾸어야만 하는 경우가 생긴다. 바꿀 경우 40 대신에 기호 상수 STUDENT를 사용하면 #define 부분만을 수정하면 된다.
확인 문제 2
/* 선행처리기 연산자 #을 사용한 대치 문자열의 확장 */
#include <stdio.h>
#define PRINT(data) printf(#data" is %d\n", data)
int main(void)
{
int temp = 25;
PRINT(temp);
PRINT(temp * 2);
PRINT(5 + 5);
return 0;
}
확인문제 3
/* 선행 처리 지시문 #deine을 사용한 예제 프로그램 */
#include <stdio.h>
#define BEL '\007'
#define BEGIN {
#define END }
#define STRING "This is a sample program about preprocessor"
#define LOOP while
#define WRITE printf
#define SUM (i + 10)
main( )
BEGIN
int i, j;
WRITE("%s\n", STRING);
i = 0;
LOOP(i <= 2)
BEGIN
WRITE("%c%c%c%d\n", BEL, BEL, BEL, SUM * 10);
i++;
END
END
(2) 인수 포함 매크로
#define문을 사용해서 인수를 포함하는 매크로를 정의한다.
매크로명 매크로 정의 본체
#define upper(×) ((×-('a'-'A'))
형식인수 형식인수
라고 정의하고, 프로그램 내에서
upper('c')
라고 매크로 호출하면 프리 프로세서는 이것을
(('c')-('a'-'A'))
라고 전개한다.
일반적으로 인수를 포함한 매크로 정의는
#define 매크로명(형식인수) 형식인수를 포함한 매크로 정의 본체
라고 표시되며 매크로는
매크로명(실인수)
로 실행된다. 이렇게 하여 매크로 호출 위치에 매크로 정의 본체가 전개된다.
매크로를 정의할 때 매크로명과 ()의 사이에 공백을 두면 안 된다. 예로 #define upper(X) 등으로 정의하면 (X)는 인수가 아니라 치환된 문자열이라 판단된다. 인수가 복수일 때는 콤마(,)로 분리한다. 예를 들어,
#define example(x,y) (x)*(x)+(y)*(y)
로 정의된 매크로를 다음과 같이 호출하면
a = example(1.0, 5.0)
a에 (1.0)²+(5.0)²의 값이 들어간다. 일반적으로 매크로 정의 본체에 사용되는 인수는 안전을 위해 ()으로 묶는 것이 좋다. 예를 들면,
#define abc(X) X*5
라고 정의하고
abc(a+10)
라고 호출하면
a+10*5
와 같이 의도하는 의미와 다르게 전개되어 실행되지만
#define abc(X) (X)*5
라고 정의하면,
(a+10)*5
와 같이 원래의 의도대로 전개된다.
예제 1. 절대값을 구하는 매크로 abs(X)를 작성하여라.
● 프로그램
#include <stdio.h>
#define abs(X) ((X)>0) ? (X) : -(X))
main( )
{
printf("int data abs ;%d\n", abs(-10));
printf("double data abs ; %f\n", abs(-10.0));
}
● 결과
int date abs ; 10
double data abs ; 10.000000
확인문제 4
/* 전달 인수를 갖는 매크로 형식을 사용한 예제 프로그램 */
#include <stdio.h>
#define INCREMENT(x) x+1
#define DECREMENT(y) y-1
main( )
{
int i = 111, j = 222;
printf("%d === %d\n", i, INCREMENT(i));
printf("%d === %d\n", j, DECREMENT(j));
}
확인문제 5
#define PI 3.14159
#define MAX(x, y) (x > y) ? x : y
#define SQUARE(x) (x * x) /* ==> ((x) * (x)) */
#define CIRCLE_AREA(r) (2 * PI * r)
main()
{
int i = 5;
float area;
printf("%d\n", MAX(i, 10));
area = CIRCLE_AREA((float) i);
printf("%5.2f\n", area);
while (i) /* squares of i from i to 1 : ERROR? */
printf("%d\n", SQUARE(i--));
}
응용문제 1
※ 위 while문을 debugging 하시오.
while (i--) printf("%d\n", SQUARE(i+1)); /* ERROR? */
응용문제 2
※ SQUARE(x)를 (x * x)로 하는 것과 x * x로 할 때 차이점은?
100 / SQUARE(5)
(3) 매크로와 함수
인수를 포함하는 매크로와 함수는 비슷한 기능을 갖고 있다. 예를 들어, 절대값을 구하는 매크로는
#define abs(X) ((X)>0?(X):-(X))
라고 표현되지만 이것을 함수로 쓰면
abs(int X)
{
return(X>0 ? X : -x);
}
라고 표현된다.
예제 1에서와 같이 매크로의 인수는 형을 가지지 않는 단순한 문자열이기에 동일한 abs(X)이고 X가 정수이던 실수이던 절대값을 구할 수가 있다. 그러나 함수로는 함수형과 인수형을 정하고 그 형만 취급한다. 그러나 매크로에도 결점은 있다. 예를 들면
abs(X++)
라고 매크로 호출하면
(X++)>0 ?(X++):-(X++)
라고 전개되어 X++가 두 번 실행된다. 이것을 매크로 부작용이라고 한다.
매크로와 함수의 차이점을 정리하면 다음과 같다.
-
매크로의 인수는 형을 갖지 않으므로 어떤 형에도 적용되지만 함수는 형에 의존한다.
-
매크로 전개는 그 위치에서 매크로 본체를 바꾸어 넣기 때문에 호출된 횟수만큼 in-line 코드가 생성되어 메모리는 낭비되지만 실행시의 함수 호출과 같은 overhead가 없다.
-
매크로는 한 줄에서 쓸 수 있는 범위(\을 행 끝에 위치하고 다음 행에서 계속 할 수 있다)에 국한되므로 복잡한 처리는 할 수 없다.
-
매크로는 인수 전달 방법에 의한 부작용을 일으킬 때가 있다.
※ macro는 memory 낭비 대신 실행이 빠르고, 함수는 memory 절약 대신 실행이 늦다.
4. 기타 : #undef, #if, #ifdef, #ifndef, #else, #endif
#undef --- #define에 의하여 정의된 이름을 해제한다.
#define MAX 100
#define PI 3.14
...... /* MAX는 100으로 정의됨 */
#define MAX 200
...... /* MAX는 200으로 중복 정의됨 */
#undef MAX
...... /* MAX는 100으로 정의됨 */
#ifdef, #ifndef, #if, #else, #endif
#ifdef MAVIS
#include "horse.h"
#define STABLES 5
#else
#include "cow.h"
#define STABLES 15
#endif
#if SYS = "IBM"
#include "ibm.h"
#endif
확인문제 6
/* #ifdef 지시문을 사용한 조건부 컴파일 */
#include <stdio.h>
#define DEBUG
#define COUNT 4
int main(void)
{
int i;
int total = 0;
for (i = 1; i <= COUNT; i++)
{
#ifdef DEBUG
total += i * 3;
printf("[%d] partial sum1 = %d\n", i, total);
#else
total += i * 5;
printf("[%d] partial sum2 = %d\n", i, total);
#endif
}
printf("total = %d\n", total);
total = 0;
#undef DEBUG
for (i = 1; i <= COUNT; i++)
{
#ifdef DEBUG
total += i * 3;
printf("[%d] partial sum1 = %d\n", i, total);
#else
total += i * 5;
printf("[%d] partial sum2 = %d\n", i, total);
#endif
}
printf("total = %d\n", total);
return 0;
}
댓글