IT 과학/C언어

C언어 | 프리 프로세서

곰뚱 2020. 2. 16.

 

 

 

프리 프로세서는 컴파일에 앞서 소스 코드를 지시된 내용에 따라 미리 처리하는 프로세서이다. 이 프리 프로세서에 지시를 주는 제어문으로서 #로 시작하는 몇 가지 문이 있지만, 본 내용에서는 가장 대표적인 #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);
      }
}

728x90

 

 

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;
}

 

 

 

그리드형

댓글