본문 바로가기
기술자료/C C++

C/C++Pre-processor(전처리기)이 대해 ...

by 알 수 없는 사용자 2009. 8. 11.
728x90
반응형

컴파일을 시도할 경우 컴파일이 실행되기 전에 전처리기 명령부터 처리된다.
전처리기는 # 로 시작하고 ; 를 붙이지 않으며 보이지 않게 소스 코드를 변경하며
컴파일러에게 지시를 내릴 수도 있다.

각각은 여기서는 명령어라는 용어를 사용했지만 정확하게는 
directive(지시자) 라고 하는 것이 나을 지 모르겠다.

#include <header.h>

가장 흔히 볼 수 있는 전처리기이다. 
해당 파일을 찾아서 컴파일러가 그 파일이 마치 현재 컴파일하는
소스 코드에 포함되어 있는 것같이 해준다. 
<> 는 표준 헤더 파일일 경우에 설정되어 있는 폴더에서

헤더 파일을 찾으며 “ ” 는 그 외 폴더에서 찾을 수 있는데 
최우선으로 현재 프로젝트 폴더에서 찾게 된다.

#define

define 문은 여러 경우에 사용될 수 있는데 일반적으로 문자열 대치에 사용된다.

#define MAX 512

 라고 하면 소스 코드에 있는 MAX 라는 문자를 모두 512 로 대치한다.

int Arr[MAX];

 는 실제 컴파일러가 컴파일할 때는

 int Arr[512];

 로 대치될 것이다. 이것은
 
 #define MAX 512

  int Arr[MAX];가
  
  int Arr[512]; 로 바뀐다고 생각하면 된다.

대치되는 문자 없이 그냥 #define MAX로 사용되는 경우도 흔하다. 
이것은 MAX 라는 문자열을 정의하지만 대치시키지는 않는다.

이렇게 사용하는 경우는 #ifdef ~ #elif ~ #else ~ #endif 문과 
같이 사용되면 상당히 유용하게 사용된다.

#ifdef MAX

       cout << “Max is defined!” << endl;

#else

       cout << “Max is not defined!” << endl;

#endif

이렇게 사용하면 #define MAX 한 줄로 여러 버전의 실행 파일을 만들 수도 있다.

이런 것을 조건부 컴파일이라고 한다.

마지막 #endif 를 넣는 것도 잊지 말자.

#ifdef MAX

#elif defined(MIN)

#else

#endif

문이나

#if defined(MAX)

#elif defined(MIN)

#else

#endif

문등의 예제를 잘 봐두기 바란다.

¨       관련된 주요 전처리기 명령어

 

¨       #ifdef 식별자

¨       #ifndef 식별자

¨       #undef 식별자

¨       #if defined(식별자)

¨       #if !defined(식별자)

¨       #elif

¨       #else

¨       #endif

#ifdef 등으로 식별자를 사용했으면

#undef 식별자

문으로 undef 하는 습관을 기르자. #undef 이후부터 
그 식별자는 정의되지 않은 것으로 간주된다.

예제) http://blog.naver.com/xtelite/50018568445

 
포함감시(Inclusion Guard)


여러 개의 헤더 파일(.h 또는 .hpp)과 구현 파일(.c 또는 .cpp) 
들이 있을 경우 헤더 파일이 중복 포함되는경우가 많다. 특히 
C/C++에 입문한 지 얼마 안되는 사람들에게서 자주 나타나는 데 
이런 경우 포함감시를 사용한다. 사실 포함감시 기능은 만드는 
모든 헤더파일에 적용하도록 하자.

#ifndef FILENAME_H

#define FILENAME_H

// 여기에 헤더 파일의 모든 내용을 넣는다

#endif

 
이렇게 해 두면 두 번째 포함될 때 #ifndef 가 거짓이 되어 이 
헤더 파일이 포함되지 않는다.

즉,

#include “filename.h”

#include “filename.h”

와 같이 하더라도 두 번째 헤더파일은 포함되지 않을 것이다. 
FILENAME_H 부분은 관례상 파일명을 이용해서 이렇게 명명한다. 
_FILENAME_H 나 __FILENAME_H__ 등 잘 사용되지 않는 문자열을 
파일이름을 사용해서 만드는 것이다.

 
매크로 함수(Macro function)

 
매크로 함수도 전처리기로 흔히 사용된다. 
다만 디버깅이 힘들다는 단점 때문에 점점 사용되지 않고 있기도 하다.

특히, C++의 경우 엄격한 형 검사를 하게 되는데 매크로 함수를 
사용하게 되면 그 기능을 사용할 수 없으니 피해야 한다.

 

#define CUBE(x) ((x)*(x)*(x))

int x, y;

= CUBE(x); 는

int x, y;

= (x) * (x) * (x);

로 대치될 것이다. 매크로 함수를 사용한다면 
()를 남발하는 습관을 키워야 한다.

왜 그런지는

= CUBE(3+4); 와 같은 경우에 직접 대치해 보면 알게 될 것이다.

 
C++ 사용자는 매크로 함수보다는 template이나 inline 함수를 사용해야 한다.


문자열 조작

#define SAY(x) printf(#x)

SAY(Hello, world!);

와 같이 식별자 앞에 # 를 붙이게 되면 자동으로 “x” 와 같이 
“”로 둘러 싸 준다.  결과적으로

printf(“Hello, world!”); 로 대치될 것이다.

 

문자열 결합


## 는 두 개의 문자열을 결합해 준다.

#define Print(x) Print ## x

와 같이 된 경우

 

Print(One) 을 사용하면 PrintOne 이라는 문자열로 대치되고 
Print(Two) 는 PrintTwo 라는 문자열로 대치된다.
잘 사용하면 아주 유용한 기능이 된다.

 

ASSERT()

대부분의 컴파일러는 ASSERT() 매크로를 가지고 있다. 
여기서는 직접 하나 만들어 보자.

#ifndef DEBUG

#define ASSERT(x)

#else

#define ASSERT(x) \

       if ( ! (x) ) \

       { \

             printf(#x); \

             printf(“ is NULL on line %d in file %s”,
         __LINE__, __FILE__); \
       }

#endif

 

이 코드의 위에

#define DEBUG

한 줄 포함하면

#define ASSERT(x)

로 아무 일도 하지 않고 DEBUG가 정의되지 않으면 그 아래 함수가 정의된다.

즉, 디버그때만 코드가 생성되고 릴리즈시에는 코드가 생성되지 
않게 할 수 있는 것이다.

여러 줄이 필요할 때는 \ 가 사용되었다는 것에 유의하자. 
또한 내장 매크로인 __LINE__ 이나 __FILE__ 은 다음에 설명한다.

 

**내장 매크로

컴파일 시에 컴파일러가 미리 정의하고 있는 매크로들이 있다.

각각 오른 쪽에 있는 내용으로 대치된다.

 

__DATE__ : 컴파일하는 날짜

__TIME__ : 컴파일하는 시간

__LINE__ : 현재 컴파일하고 있는 줄 번호

__FILE__ : 현재 컴파일하고 있는 파일의 이름

 

#error

컴파일러는 이 명령을 만나게 되면 해당 메지시를 출력하고 컴파일을 중지한다.

C++ 컴파일러에서만 동작하게 하는 다음 코드를 참조하자.

 

#if !defined(__cplusplus)

#error C++ compiler required.

#endif


__cplusplus 는 C++ 컴파일러일 경우에 정의되는 내장 매크로이다.


#pragma

#pragma 는 컴파일러마다 고유하게 사용할 수 있는 명령어이다. 
  따라서 그 문법은 컴파일러마다 다르고 그 종류도 많다.

 

예를 들어

#pragma once

같은 경우 위의 포함감시 기능을 컴파일러가 알아서 해 준다. 
즉, 한 번 include 된 헤더 파일은 중복해서 포함되지 않도록 
 컴파일러가 처리해 준다.


 매크로 함수 정의시 주의할 점 

① 매크로 함수에 사용되는 모든 인자에는 반드시 괄호를 해두는 
것이 안전한 정의방법이다. 예를 들어 다음의 매크로 함수를 보자. 

 이 경우 괄호를 사용하지 않고 정의한 것으로, 부작용이 발생한 것이다. 
 위 매크로 함수의 전개 후를 보면 알겠지만, 결과는 예상했던 21이 아니라 
 11이 나온다. 이러한 부작용을 방지하기 위해 가능하면 모든 인자에 
 괄호를 해두는 것이 좋다. 

② 매크로 함수 내에서는 단일 연산자인 증가 연산자(++) 또는 감소 
  연산자를 사용하지 않는 것이 좋다. 다음의 예를 보자. 

#define SQR(x) ((x)*(x))

        
void main()
        {        
        int i=2;        
        printf("%d\n", SQR(++i));}
이렇게 매크로 함수 SQR()을 정의하고, 매개변수로 ++i 를 
주었을 때 전개 결과는 다음과 같다.

         printf("%d\n", (++i)*(++i)); 

이를 보면 알겠지만 증가 연산자가 한 문장 안에서 동시에 쓰였다.

원하는 내용은 i 가 1증가하는 것이지만 실제로는 2가 증가하게 된 것이다.

실제로 결과값은 3*4==12 가 나온다.

 매크로 함수를 사용하는 이유 

- 매크로 함수를 사용하는 이유는 간단한 함수를 사용할 때 
굳이 실제의 함수를 만들지 않고, 매크로 함수로 만들어 사용함으로써 
보다 간단한 프로그래밍을 할 수 있기 때문이다. 물론 복잡한 기능을 가지는 
함수들은 매크로 함수로 만들 수 없겠지만, 간단한 연산을 하는 정도의 함수는 
매크로 함수로 작성하는 것이 좋다. 가령 간단한 연산을 하는 함수가 굉장히 
빈번하게 프로그램 곳곳에서 호출된다고 생각해 보자. 함수가 호출될 때 
생기는 여러 가지 작업들(예를 들어 자동변수를 만들었다 없애는 등의 작업) 
때문에 상당히 많은 시간을 소비하게 된다. 특히 반복문 안에서 그 함수를 호출하게 되면, 
그 시간은 더욱 길어진다. 하지만 이런 함수를 매크로 함수로 바꾸게 되면, 
실제적으로 함수를 호출하는 일이 없어서 빠른 시간에 작업을 처리할 수 있게 된다. 

물론 단점도 있는데, 함수는 하나의 루틴을 계속 불러서 사용하지만 매크로 
함수는 모두 치환이 되기 때문에 실행 파일의 크기가 커질 수도 있다. 

D) 매크로 함수의 용도 

① 중복되는 수식을 매크로 함수로 간결하게 기술 

- 프로그래밍을 하다보면 특정 수식이 계속해서 쓰이는 경우가 있는데, 
이것을 따로 함수로 만들어 쓰자니 실행속도가 문제될 것 같고, 그대로 
수식으로 쓰자니 프로그램이 너무 복잡해 질 것 같은 상황에서, 
이러한 수식을 매크로 함수로 치환하면 소스가 판독하기 쉽고 간결해진다. 

② 표준 라이브러리 함수의 특정 실매개변수를 특정 값으로 지정한 경우 

- 프로그램을 작성하다 보면 표준 라이브러리 함수의 특정한 매개변수를 
항상 특정한 값으로 지정하여 쓰는 경우가 많다. 예를 들어 HGC에서는 
floodfill() 함수의 3번째 매개변수가 항상 1일 수 밖에 없다. 이처럼 
floodfill(x, y, 1) 과 같이 매번 1이라는 값을 지정해 주기가 번거러울 
때 매크로 함수를 쓰면 된다. 

     #define  paint(x, y)  floodfill(x, y, 1)>③ 매개변수의 데이터형을 제한하고 싶지 않을 때

- 실제함수는 실매개변수의 데이터형이 제한되어 있지만, 매크로 함수는 
  데이터형이 제한되어 있지 않으므로 모든 데이터형을 매개변수로 
  사용할 수 있다. 



728x90