프로그래밍/C

4.11 C 프리프로세서(The C Preprocessor)

피노리코 2014. 11. 2. 05:35

C는 프리프로세서(preprocessor)에 의해 일종의 언어 확장 기능을 제공한다. #include 먕량에 의해 컴파일시 어떤파일을 실어 올수 있으며 #define 명령으로 긴 문자열을 하나의 심벌로 대치하여 사용할수도 있다. 이 절에서는 첨자를 가진 매크로 정의와 조건부 컴파일에 대해 설명하겠다.


4.11.1 파일 삽입


파일 삽입 기능은 #define과 선언 집합을 처리하기 쉽게 해 준다. 다음과 같은 명령은 filename 파일의 내용이 이 위치에 복사된 것과 똑같은 동작을 하게 해준다.


#include "header"


혹은


#include <filename>


소스파일 앞부분에 여러 개의 #include 명령이 올수 있다 예를 들어 <stdio.h>등의 하이브러리 함수나 #define 또는 외부 변수의 선언등이 올수있다 #include는 커다란 프로그램에서 선언을 결합하기 위해 주로 사용된다 모든 소수 파일이 똑같은 정의와 변수 선언을 제공받게 됨을 보장함으로써 귀찮은 버그의 발생 확률을 감소기켜 준다. 그러나 include 파일에 변화가 생겼을 경우 그것을 포함하는 모든 소스파일을 다시 컴파일 해야한다.


4.11.2 매크로 치환


다음과 같은 정의는


#define 이름 문자열


가장 간단한 종류의 매크로 치환으로서,이렇게 하면 긴 문자열 대신 상수명을 쓸수 있다 #define 다음의 이름은 변수 이름과 같은 형을 가지며 텍스트는 아무것이나 상관 없다. 치환될 텍스트는 이름 뒤에 위치하는데 한행을 넘어갈 정도로 긴 경우 계속 표시를 나타내는 \를 행의 끝에 삽입하면 몇행이라도 연속해서 쓸수 있다. #define 으로 정의된 이름의 범위는 컴파일되는 파일 내에서만 유효하다. 이름은 다시 정의될 수도 있고 정의는 이전의 정의를 재사용할수 있다. 그리가 정의된 이름인 경우 printf("YES"또는 YESMAN에서 매크로 치환이 일어나지 않는다.

이런 몇가지 경우만 제외하고 어떤종류의 이름도 치환될 수 있다. 예를 들면


#define forever for(;;) /* infinite loop */


는 forever란 이름의 무한루프를 지정한다 또 한 첨자로 지닌 매크로 정의도 가능하다 예를 들면


#define MAX(A,B) ((A) > (B) ? (A) ; (B))


이것은 함수호출처럼 보이나 라인 내부의 코드로 확장하는 역할을 수행한다 즉 다음과 같이 변환하는 것이다.


x=max(p+g,r+s);



X=((p+q) >(r+s) ? (p+q) : (r+s));


로 치환된다 이 매크로는 어떤 데이터형에도 적용 가능 하므로 함수의 경우처럼 서로 다른 데이터 형에 대해서 서로 다른 종류의 max가 필요하지 않다. max의 확장을 관찰해 보면 약간의 함정을 관찰할 수있다. 수식은 두번 계산되므로 입출력문이 증가 연산자를 포함할 경우 좋지 않은 결과를 가져온다 예를들어


max(i++,j++) /* WRONG */


는 두번의 증가를 가져오게 되므로 좋지 않다. 즉 치환되고 나면 내부에서 몇 번 계산하는지가 한눈에 보이지 않으므로 조심해야 한다. 계산순서를 올바르게 하기 위해서는 괄호에도 주의를 기울여야 한다.

다음 매크로를 정의하고 square(z+1)이라는 문장을 사용했을때의 결과를 생각해 보자.


#define square(x) x * x /* WRONG */


이런 약간의 단점에도 불구하고 매크론느 매우 유용하다 실제적인 예로<stdio.h> 와같이 getchar와 putchar같은 함수들이 정의되어 있는 매크로는 프로그램 수행속도를 크게 향상시켜 준다.

이름(name)은 #undef 명령에 의해 정의가 해제될 수도 있다 그 예는 다음과 같다


#undef getchar


int getchar(void ) { ... }


따옴표 속에 있는 변수나 상수를 치환하기 위해서는 #를 사용해야 하는데 다음 예는 #의 사용법을 보여준다.


#define dprint(expr) printf(#expr "=%g\n",expr)


이 매크로가 다음 명령으로 수행되면


dprint(x/y);


매크로는 다음과 같이 확장된다.


print("x/y" "=%g\n",x/y);


그 효과는 다음과 같다


print("x/y =%g\n",x/y);


즉 #을 붙이면 변수가 아닌 문자열로서의 치환이 일어나는데 문자열 중의 첨자에서 "는 \로치환되고 \는 \\로 치환되므로 그 결과는 문법에 어긋나지 않는다.

##연산자는 매크로 확장시 실제 첨자를 연결하는 역할을 한다. 치환될 텍스트가 ##로 연결되어 있으면 ## 앞의 텍스트와 ## 뒤의 텍스트가 여백없이 연결된다.  예를들어 두개의 첨자를 연결하는 paste라는 매크로를 살펴보면


#define paste(front,back) front # # back


따라서 paste(name,1)은 name1이라는 이름을 만들어 낸다. ##의 자세한 사용에 관해서는 부록A에서 설명하겠다


예제 4-14 두개의 첨자 x,y를 t형으로 상호치완하는 매크로 swap(t,x,y)를 작성하라


4.11.3 조건부 포함


프리프로세서 명령어도 컴파일시의 조건에 따라 포함할지 포함하지 않을지를 선택할 수있다.#if 명령은 정수형 표현(sizeof,casts,enum 제외)을 계산하여 그결과가 0이 아니면 #endif나 #elif 또는 #else 나오기 전까지의 라인들을 포함시킨다.defined(name)문은 이름(name)이 정의 되었으면 1의 값을 아니면 0의 값을 갖게 된다. 예를 들어 hdr.h라는 파일을 단 한번만 포함하고 싶다면 다음과 같이 하면 된다.


#if !defined(HDR)

#define HDR


/* contents of hdr.h go here */


#endif


일단 hdr.h 파일이 포함되면 HDR 이라는 이름이 정의된다. 다음에 또 포함시키려고 하면 이름이 정의되었으므로 #endif로 뛰게 된다 비슷한 방법으로 파일의 중복 포함을 방지할수 있다

다음 프로그램은 시스템이 어떤 버전의 헤더파일을 사용할 것인지를 선택할 수있게 해준다.


#if SYSTEM == SYSV

    #define HDR "sysv.h"

#elif SYSTEM == BSD

    #define HDR "bsd.h"

#elif SYSTEM == MSDOS

    #define HDR "msdos.h"

#else

    #define HDR "default.h"

#endif

#include HDR


#ifdef #ifndef 명령은 이름이 정의되어 있나를 알아보는 특별한 기능을 수행한다. 첫번쨰 예제의 #if는 다음과 같이 쓸수도 있다.


#ifndef HDR

#define HDR



/* contents of hdr.h go here */


#endif