sonumb

C Preprocessor 종합 선물세트 본문

개발자 이야기/C/C++

C Preprocessor 종합 선물세트

sonumb 2008. 2. 13. 22:23

목차.
1.File Inclusion
2.Macro Substitution
3.Contitional Inclusion
4.기타


Preprocessor의 핵심:
     `Preprocessing`은 링크전도 아닌 컴파일타임도 아닌 컴파일타임 전에 일어난다는 것이다.


1.File Inclusion
제목을 영어로 거창하게 적어놨지만 역시 핵심은 파일을 갖다 붙이겠다는 말이다.

#include <filename>  or  #include "filename"

<,>로 묶여진 파일은 implementation-defined rule에 의해 해당파일을 찾는다.
","로 묶여진 파일은 보통 해당파일(즉 #include "filename"이 쓰여진 파일)이 있는 폴더내에서 찾는다.

2.Macro Substitution
#define
#define 은 이래 저래 쓸데가 많다.

#define SQUARE(x) ((x)*(x))   // Macro Function
#define _OUR_HEADER  // 보통 Condition 매크로랑 같이 쓰인다.
#define PI 3.14159         // Macro Constant


#undef
아래의 예문만 봐도 이해가 간다.

#define PI 3.14159
#undef PI
..
printf("%lf",PI);   // error!!


#, ##
위의 두개는 문자열과 관계있고, #define 다음에 와야 한다. 소스내에 있으면 syntax 에러발생.
아래의 예제를 보면 이해 될 것이다.

#include <stdio.h>

/*
 *  ##전후에 tab이나  공백있을시 무시하며 앞뒤 문자열을 붙여준다.
 *  문자열을 붙여주긴 하되, 문자열상수로 만들어주진 않는다.
 * 즉 아래의 예제는 lstr[i]로 된다. "lstr[i]" 가 아닌거임. -_-;
 */
#define GLUE(lstr, i)    lstr ##[## i ##]

//var 자체가 string 즉 문자열 상수로 치환된다.
#define MakeStringr(var) #var 

int main( void )
{
  int arr[3] ={ 0,1,2};
  printf( 
    MakeString( arr[1] )
    " :%d\n", GLUE( arr, 1 )
  );
  return 0;
}

위의 메인 소스는 컴파일러( 정확하게 말하자면 전처리 해석기)에 의해 소스가 다음과 같이 치환(!)된다.

int main( void )
{
  int arr[3] ={ 0,1,2};
  printf(  
    "arr[1]"
    " :%d\n", arr[1]
  );
  return 0;
}

그러고는 컴파일을 하겠지?
이제는 짐작이 올것이다.
결과는

arr[1] :1



3.Conditional Inclusion
조건처리에 관한것이다. if( condition ) ... else if( condition ) ... 와 비슷하다.

#define  _ABC
#define _BBC

// 참고: #ifndef _ABC -->#if !defined(_ABC)

#ifdef _ABC    // #if defined(_ABC)
    // 위 조건에 따라 이것이 선택된다.
   #define GLUE(lstr, i)   lstr[##i]  
#else if (!defined( _ABC) && defined( _BBC ))
   #define GLUE(lstr, i)   *(lstr+##i)       // 선택이 되지 않는다.
#endif

#define MakeString( var ) #var

#ifndef ,#ifdef 과 #if 와의 차이는 여러 조건을 넣을 수 있느냐 없느냐다.
#else if (!defined( _ABC) && defined( _BBC ))  이것 처럼


이런 것도 된다.

#define TRUE 1
#define FALSE 0

#define  _ABC TRUE

#if _ABC == TRUE
  #define GLUE(lstr, i)   lstr[##i]
#elif _ABC == FALSE
  #define GLUE(lstr, i)   *(lstr+##i)
#endif

아래예제는 안된다. 전처리기는 문자열 비교 자체가 안되기에 컴파일시 Error가 발생한다.

//#define  _ABC
#define _BBC "true"

#ifdef _ABC // #if defined(_ABC)
 #define GLUE(lstr, i)   lstr[##i]
// 당연히 안된다.. 문자열 비교와 어떻게 같은가?
#elif _BBC == "true"
 #define GLUE(lstr, i)   *(lstr+##i)
#endif

결론은 전처리프로세서는 멋지다는 것이다. 쿨럭.;;

농담이고..
조건 비교와 조건 비교시 상수 비교, 논리산술(and, or , not) 정도는 된다것을 기억해두면된다.


4.기타
한꺼번에 때려 넣어 봤다.
몇가지 directive 랑 매크로 상수(Macro Constant)에 관한 내용이다.
매크로 상수는 __name__ 와 같은 형태이다.

#include <stdio.h>

// `다음 라인`은 43번이다! 그리고 소스파일의 이름은 `new.c`이다
// 라고 '컴파일러'에게 알려준다.
#line 43 "new.c" 

// 해당 컴파일러가 C 컴파일러인지에 대한 매크로 상수
// 컴파일러가 C++컴파일러면 __STDC__ 안의 매크로가 실행될것이다.
 #ifndef __STDC__
/*
 *  에러 난경우 IDE나 콘솔에
 *  #error 다음 개행 전까지의 문자열을 출력
 *  사실 C Compiler가 아닌 거임. <- 이것이 출력된다.
*/
#error 사실 C Compiler가 아닌 거임.
#endif

int main( void )
{
 printf(
  "Time:      %s\n"
  "Timestamp: %s\n"
  "Date:      %s\n"
  "Line:      %d\n"
  "File:      %s\n",
  __TIME__,  //현재 소스파일이 가장 최근 컴파일 된 시간.
  __TIMESTAMP__ , //현재 소스파일이 마지막으로 수정된 시간.
  __DATE__, // 현재 소스 컴파일 한 날짜.
  __LINE__,   // 현재 __LINE__ 매크로 상수가 위치한 라인번호
  __FILE__ // 현재 소스파일의 이름.설정에 따라 path를 포함하기도 한다.
 );

 return 0;
}

그 이외에도 #using 이나 #pragma 등이 있다. using은 `c++ 전처리기`라서, pragma는 다음과 같은 이유로 설명을 하지 않겠다. 대체적으로 표준이 아니라 운영체제 종속적이거나, 컴파일러 제조사의 취향(?)에 맞게 directive가 설계되어지는 것이기에(implementation-defined) 차라리 제조사 Manual을 보는게 맞다.
 

결론을 내려보자

매크로는  편리하면서도 화려하게 만들수 있으나, 나중에 되면 오히려 혼란만 가중시키게 되는 복잡성도 가지고 있다.( Condition Inclusion 같은...)매크로 펑션처럼 간단하면서도 편리하지만 그 이면에는 디버깅이 안된다는 해악도 같이 존재한다. 또한 말그대로 Preprocessor이기에 프로젝트 build시 컴파일 부담을 가중시킬수도 있다.

 아무쪼록 평소에 간단한 퀴즈 같은 프로그램들을 많이 짜보면서 손익을 스스로 체득하는 것이 바람직하겠다.
반응형