[ C 언어 스터디 ]

 

자료출처: 핵시 길잡이 C 프로그래밍 언어

도서출판: 성안당

 

3. 선행처리기 ( Preprocessor )

선행처리기란 프로그래머와 컴파일러 사이에서 매개 역할을 수행하며, C 프로그램을
컴파일하기 전에 프로그래머가 작성한 원시 프로그램에 특정하게 정의된 각종 내용을
삽입시키는 기능을 하는 처리기를 말한다.
선행처리기 정의는 # 기호로 시작된다. 프로그램 상에서 정의하는 위치는 특별히 정
해져 있지 않지만 일반적으로 첫 칸부터 지정하며 그 문장의 끝에는 세미콜론 ( ; ) 을
붙이지 않는다. 또한 하나의 명령은 한 줄에서만 가능하다. 예를 들면,

# define PI 3.141592

라고 정의하였다면 선행처리기는 프로그램에 사용된 PI 를 모두 3.141592로 치환한 후
원시 코드를 번역하게 된다. 여기서 # define 문은 상수를 선언한 것처럼 보이지만 사
실상 선언문이 아니라 치환하라는 의미를 가지는 매크로 ( macro ) 이다.
매크로 등의 선행처리기를 사용하는 근본적인 이유는 보다 효율적인 프로그램을 작
성할 수 있고, 프로그램의 이식성과 이해를 증진시킬 뿐만 아니라 일반 함수를 사용할
때보다 수행 속도가 빠르기 때문이다. 이러한 선행처리기는 매크로의 정의 ( # define ),
파일의 첨가 ( # include ), 조건부 컴파일 등이 있다.

 

★ 매크로 정의 ( # define ) ★

복잡한 문장이나 여러 줄에 걸쳐 기술된 문장을 하나의 문장으로 정의하여 사용하는
것을 매크로 ( macro ) 라 한다. 일반적으로 매크로는 변수와 구분하기 위하여 대문자를
사용하여 기술한다. # define 문은 이러한 매크로를 원시 프로그램에 정의하는 데 사용
하는 선행처리기이다. 즉 # define 으로 정의되어 있는 매크로를 원시 프로그램 내에서
검색하여 검색된 매크로를 모두 정의한 문자열로 치환시킨다. 따라서 C 언어에서 프로
그램의 처음에 매크로명을 정의해 두면 나중에 그 값을 일일이 지정하지 않아도 된다.

# define 사용 시 몇 가지 유의해야 할 사항이 있다.

1. # define 정의는 프로그램에서 첫 칸에 # 기호로 시작되며 원시 프로그램 내의
어느 곳에서나 지정할 수 있다. 이때의 유효 범위는 정의한 곳에서부터 그 프로그램
의 끝까지이다.

2. 매크로명은 일반 변수와 구별하기 위해 보통 대문자를 많이 사용한다. 이때 지정
하는 매크로명 중간에 공백을 두어서는 안 된다. ( 매크로명을 소문자로 지정하여
도 문법적인 오류 ( error ) 는 발생하지 않는다. )

3. C 언어에서는 문장의 끝을 세미콜론 ( semicolon  : ' ; ) 으로 나타내는 데 반해
# define문의 끝에는 세미콜론 ( ; ) 을 붙이지 않는다. 만약 세미콜론을 붙이면 세
미콜론까지도 치환할 문자열로 간주한다. 이때 컴파일러 에러는 발생하지 않지만
프로그래머의 의도와 달라질 수 있다.

4. # define 지정은 한 줄 내에서만 가능하다. 만약 한 줄에 매크로명과 치환 문자
열을 전부 쓸 수 없으면 줄의 끝에 역슬래쉬 ( \ ) 를 하여 다음 줄에 계속 지정할
수 있다.

5. 매크로를 문자열 상수 즉, 이중 인용부호 ( " " ) 사이에 지정하면 # define 에 지정
한 치환 문자열로 치환되지 않는다.

6. 매크로명과 인수를 기입하기 위한 괄호 사이에 공백 ( blank ) 을 두어서는 안 되며,
문자열 전체를 괄호 속에 넣어서도 안 된다.

< 인수가 없는 매크로 정의 >

인수 없이 사용되는 # define의 일반 사용 형식은 다음과 같다.

형식
# define < 매크로명 >   < 치환 문자열 >

 

1. # define TRUE  1
2. # define PI  3.141592
3. # define HELLO  " Hello , C Porgramming "
4. # define JUNGSU  int
5. # define BEGIN  {

선행처리기를 사용하여 문자 상수와 문자열 상수를 정의할 때에는 다음과 같이 각각
단일 인용부호 ( '    ' )와 이중 인용부호 ( "    " )로 묶어서 처리한다.

# define  매크로명   ' 문자 상수 '
# define  매크로명  " 문자열 상수"

# define  STOP  ' @ '
# define   STOP    " end of data "

 

다음 프로그램의 실행 결과를 나타내시오

▶프로그램

# include < stdio.h >

# define  JUNGSU   int
# define   SILSU    float
# define   WRITE   printf
# define  READ    scanf

void main ( )
{

JUNGSU   a ;
SILSU   b , c ;
READ ( " %d      %f " , &a , &b ) ;
c = a + b ;
WRITE ( " %d  +  %f  =  %f n\ ", a , b , c ) ;

}

▶실행 결과
4      3.2  Enter Button Push
4  +  3.200000 = 7.200000

 

< 인수가 있는 매크로 정의 >

선행처리기는 인수를 포함한 매크로도 허용한다. 즉 함수처럼 상황에 따라 인수를
지정하여 원하는 결과를 얻을 수 있다. 이를 인수를 갖는 매크로 또는 매크로 함수
( macro function ) 라 한다.
인수가 있는 # define 의 일반 사용 형식은 다음과 같다.

형식
# define 매크로명 ( 가인수 1,  가인수 2,  ....... )  < 치환 문자열 >

결과적으로 매크로명은 매개변수 ( parameter ) 를 갖고 있어 매크로명이 호출될 때마다
가인수 ( formal parameter ) 가 실인수 ( actual parameter ) 로 치환된다. 특히 주의할 것은
매크로명과 매개변수를 나타내는 괄호 사이에 공백이 있어서는 안 된다. 공백이 있는 경
우에는 이것도 치환 문자열로 인식하게 된다. 또한 치환 문자열에서 매개변수 내용의 전
체 및 각 요소 자체도 괄호를 삽입해야 원하는 연산 결과를 얻을 수 있다.
또한 매개변수로 ++a 와 같은 증감 연산자는 결과를 예측할 수 없으므로 사용하지
않는 것이 바람직하다.

다음의 원의 넓이를 계산하는 프로그램이다. 실행 결과를 나타내시오

▶프로그램

# include < stdio.h >

# define  PI  3.141592
# define  AREA ( R )  ( PI * ( R ) * ( R ) )

void main ( void )
{

float  radius ;
printf ( " 반지름을 입력하시오  :  " ) ;
scanf ( " %f " , &radius ) ;
printf ( " 원의 넓이 = %f \n " , AREA ( radius ) ) ;

}

▶실행 결과
반지름을 입력하시오  :  5     Enter Button Push
원의 넓이 = 78.539800

 

다음 프로그램의 실행 결과를 나타내시오

▶프로그램

# include < stdio.h >

# define  INPUT ( X )    scanf ( " %d " , &a ) ;
# define  PRINT ( X )    printf ( " %d ", a ) ;

void main ( void )
{

int a ;
INPUT ( X ) ;
PRINT ( X ) ;

}

▶실행 결과
8
8

매크로 확장인 경우에는 ( ) 를 사용하는 것이 좋다. 이것은 문법적으로 규정하는 것
은 아니지만 안전 측면 몇 프로그램의 이해도를 증진시키므로 필요에 따라서 ( ) 를 적
절하게 사용하는 것이 좋다.

다음 프로그램의 실행 결과를 나타내시오

▶프로그램

# include < stdio.h >

# define   SQUARE ( X )  X * X

void main ( void )
{

int  a = 7 ;
printf ( " SQUARE ( a + 1 ) = %d \n ", SQUARE ( a +1 ) ) ;
printf ( " SQUARE ( a ) = %d \n " , SQUARE ( a ) ) ;

}

▶실행 결과
SQUARE ( a + 1 ) = 15
SQUARE ( a ) = 49

 

다음 프로그램의 실행 결과를 나타내시오

▶프로그램

# include < stdio.h >

# define SQUARE ( X )   ( X * X )

void main ( void )
{

int a = 7 ;
printf ( " SQUARE ( a + 1 ) = %d \n ", SQUARE ( a + 1 ) ) ;
printf ( " SQUARE ( a ) = %d \n " , SQUARE ( a ) ) ;

}

▶실행 결과
SQUARE ( a + 1 ) = 15
SQUARE ( a ) = 49

 

위 예제에서 SQUARE ( a + 1 ) 을 매크로  # define SQUARE ( X ) X * X 에
대입하면 수식은 다음과 같다.
a + 1 * a + 1
여기서 연산의 우선 순위를 적용하면 다음과 같다.


이 때 a = 7 을 대입하면  7 + 1 * 7 + 1 = 15 가 된다.

 

다음 프로그램의 실행 결과를 나타내시오

▶프로그램

# include < stdio. h >

# define  SQUARE ( X )  ( X )  *  ( X )

void main ( void )
{

int a = 7 ;
printf ( " SQUARE ( a + 1 ) = %d \n " , SQUARE ( a + 1 ) ) ;
printf ( " SQUARE ( a ) = %d \n " , SQUARE ( a ) ) ;

}

▶실행 결과
SQUARE ( a + 1 ) = 64
SQUARE ( a ) = 49

 

다음 프로그램의 실행 결과를 나타내시오

▶프로그램

# include < stdio.h >

# define   SQUARE  ( X )  ( ( X ) * ( X ) )

void main ( )
{

int a = 7 ;
printf ( " SQUARE ( a + 1 ) = %d \n ", SQUARE ( a + 1 ) ) ;
printf ( " SQUARE ( a ) = %d \n ", SQUARE ( a ) ) ;

}

▶실행 결과
SQUARE ( a + 1 ) = 64
SQUARE ( a ) = 49

위 두 예제 에서 SQUARE ( a + 1 ) 을 매크로  # define SQUARE ( X )  ( X ) * ( X )
에 대입하면 수식은
( a + 1 )  *  ( a + 1 )
과 같다. 여기서 연산의 우선 순위를 적용하면 괄호가 우선 순위가 높기 때문에 다음과
같다.


따라서 a = 7 을 대입하면 ( 7 + 1 ) * ( 7 + 1 ) 이므로 결과는 64가 된다. 그러므로 매크로
확장의 경우 ( ) 를 적절하게 사용하여야 정확한 연산 결과를 얻을 수 있다.

 

★ # include ★

# include 문은 C 언어의 선행처리기 명령의 하나로서 , 파일을 포함하는 명령이다.
# include 다음에 지정한 파일명의 내용 즉 , 지정한 # include 문의 파일들을 현재의 원
시 파일에 포함시킨다. 일반 형식은 다음과 같다.

형식 1
# include   < 파일명 >

형식 1 은 파일을 검색할 때 하나 또는 그 이상의 표준 디렉토리에서 파일을 찾으며 ,
주로 \include 디렉토리에 있는 공용 파일을 포함시킬 때 선언한다. 시스템에서 제공하
는 < stdio.h > < math.h > < graphics.h > 등의 헤더 파일을 포함시킬 때 주로 사용하게
된다.

형식 2
# include    " 파일명 "

형식 2 는 프로그래머가 정의한 헤더 파일을 포함시킬 때 주로 사용된다. 이 때 " 파일
명 " 은 현재 사용 중인 디렉토리 또는 정해진 디렉토리에서 먼저 파일을 찾고 , 현재의
디렉토리에 없으면 시스템 디렉토리에서 찾게 된다.

1. # include  < stdio.h >
  ▶ 시스템 디렉토리에서 파일 ( stdio.h ) 을 찾는다.
2. # include  " stdio.h "
  ▶ 현재 사용 중인 디렉토리에서 파일 ( stdio.h ) 을 찾는다.
3. # include  " \tcwin45 \ sys \ sample.h "
  ▶ \tcwin45 \ sys 디렉토리에서 파일  ( sample.h ) 을 찾는다.
4. # include  " a : \ sample.h "
  ▶ A 드라이브의 루트 ( root ) 디렉토리에서 파일 ( sample.h ) 을 찾는다.

 

도형의 면적을 계산하는 다음과 같은 프로그램을 작성하시오. 단 A 드라이브에 파일area.h 이 작성되어 있다고 가정하고 그 실행 결과를 나타내시오

/ * File Name : a : \ area.h * /

# define  PI  3.141592
# define  TRIANGLE ( BASE , HIGH )  ( BASE * HIGH / 2 )
# define  CIRCLE ( RADIUS )  ( PI * ( RADIUS) * ( RADIUS ) )
# define SQUARE ( GARO , SERO ) ( GARO * SERO ) 

▶ 프로그램

# include  < stdio.h >

# include  " a : \ area.h "

void main ( )
{

int a = 10 ;
printf ( " 삼각형의 넓이 = %d \n " , TRIANGLE ( ++ a , 2 * a ) ) ;
printf ( " 원의 넓이 = % 10.5 f \n " , CIRCLE ( a ) ) ;
printf ( " 사각형의 넓이 = %d \n " , SQUARE ( a , a ) ) ;

}

▶ 실행 결과
삼각형의 넓이 = 121
원의 넓이 = 380 . 13263
사각형의 넓이 = 121

 

★ # undef 와 조건부 컴파일 ★

< # undef >

# undef 는 가장 최근에 정의된 매크로명을 해제할 때 사용되며 그 형식은 다음과
같다.

형식
 # undef  < 매크로명 >

만약 똑같은 매크로명이 다른 값으로 정의되었을 때 # undef  < 매크로명 > 이 정의되었다.
면 그 전에 정의된 값으로 환원되어 스택 ( stack ) 에 있어서의 push 와 pop 의 개념과 같다.

# define  MAX  100  //  매크로  MAX  정의
# undef  MAX //   매크로  MAX  해제

 

< 조건부 컴파일 >

조건부 컴파일은 특정 조건을 만족하는 경우에만 C 프로그램의 소스 코드를 번역한
다. 이러한 의미로 # if 와 # endif 이 사용된다.

형식 1

# if  수식 1
  명령문 1 ;

# elif 수식 2
  명령문 2 ;

#else
  명령문 n ;

#endif

선행처리기에서 { } 는 사용할 수 없기 때문에 # if 와 # endif 를 사용한다. 수식 1의 값이
참이면 # if 와 # else 사이의 명령문을 실행하지만 , 만족하지 않으면 # else 와 # endif 사
이의 명령문이 실행된다. 따라서 이러한 구조는 if ~ else 제어문의 구조와 비슷하다.

다음 프로그램의 실행 결과를 나타내시오

▶ 프로그램

# include  < stdio.h >

# define  NUMBER  100

void main ( )
{

int a = 5 ;

# if  NUMBER > 100
  printf ( " 100 보다 큰 수 \n " ) ;

# elif  NUMBER > 50
  printf ( " 50 < 수의 범위 < 100 \n " ) ;

# else
  printf ( " 50 보다 작은 수 \n " ) ;

# endif
  printf ( " a 의 값 = %d \n " , a ) ;

}

▶ 실행 결과
50 < 수의 범위 < 100
a 의 값 = 5

 

 형식 2 매크로가 정의된 경우

# ifdef 매크로명
  명령문 1 ;
#endif

 

형식 3 매크로가 정의되지 않는 경우

# ifndef 매크로명
  명령문 1 ; 
# endif

 

다음 프로그램의 실행 결과를 나타내시오

▶ 프로그램

# include  < stdio.h >

#define  AGE  20

void main ( void )
{

# ifdef  AGE
  printf ( " 나이 = %d \n " , AGE ) ;

# else
  printf ( " 나이가 불확실합니다. \n " ) ;

# endif

# ifndef  AGE
  printf ( " 나이가 불확실합니다 \n " ) ;

# endif

}

▶ 실행결과
나이 = 20

예제 에서 # define  AGE  20이 선언되어 있으므로 # ifdef AGE 문에서 조건을
만족하여 printf ( " 나이 = %d\n " , AGE ) ; 문장이 수행되었다. 그런데 #ifndef AGE
문장은 AGE 매크로가 정의되지 않았을 경우에만 다음 명령문이 수행되기 때문에 현
재 조건을 만족하지 않게 된다. 따라서 명령문 printf ( " 나이가 불확실합니다. \n " ) 는 수
행되지 않는다.