C++

C++ 전처리기(#) 총정리

김구티2 2024. 2. 12. 20:56

1. 전처리기의 개념

전처리기는 컴파일 전에 소스 코드를 처리하는 프로그램이다. C++에서 프로그램을 작성하는 것과 프로그램을 실행하는 것 사이에는 여러 단계가 관련되어 있다. 그렇기에 전처리기에 대한 이해를 하기 이전에, 그 단계에 대해 이해하는 것이 우선이라고 할 수 있겠다.

 

 

위의 다이어그램에서 중간 단계를 볼 수 있다. 프로그래머가 작성한 소스 코드는 먼저 파일에 저장되며, 이름을 "program.c"로 한다. 그런 다음, 이 파일은 전처리기에 의해 처리되고 확장된 소스 코드 파일은 "program.i"로 생성된다. 이 확장된 파일은 컴파일러에 의해 컴파일되고 개체 코드 파일은 "program.obj"로 생성된다. 마지막으로 링커는 이 개체 코드 파일을 라이브러리 함수의 개체 코드에 연결하여 실행 파일 "program.exe"를 생성한다.

 

2. C++에서 전처리기 지침

전처리 프로그램은 컴파일러에게 컴파일하기 전에 소스 코드를 전처리하도록 지시하는 전처리 지침을 제공한다. 이 모든 전처리 지침은 # 기호로 시작한다. # 기호는 #로 시작하는 모든 문장이 실행되기 위해 전처리 프로그램으로 이동한다는 것을 나타낸다. 우리는 이러한 전처리 지침을 프로그램의 어느 곳에나 배치할 수 있다.

 

전처리기 지침의 예로는 #include, #defin, #ifndef 등이 있다. 참고로, # 기호는 전처리기에 대한 경로만 제공하며 include와 같은 명령은 전처리기 프로그램에 의해 처리된다. 예를 들어 #include는 지정된 파일의 코드 또는 내용을 프로그램에 포함한다.

 

3. 전처리기 지침 목록

전처리기 지침 설명
#define 매크로를 정의하는 데 사용
#undef 매크로 정의 해제에 사용
#include 소스 코드 프로그램에 파일을 포함하는 데 사용
#ifdef 특정 매크로가 #define에 의해 정의된 경우, 코드 섹션을 포함하는 데 사용
#ifndef 특정 매크로가 #ifndef에 의해 정의되지 않은 경우, 코드 섹션을 포함하는 데 사용
#if 지정된 조건을 확인하는 데 사용
#else #if 실패 시 실행되는 대체 코드
#endif #if, #ifdef, #ifndef의 끝을 표시하는 데 사용

 

4. 전처리기의 유형

이러한 전처리기는 수행하는 기능 유형에 따라 분류할 수 있을 것이다. 전처리기의 지침에는 크게 4가지 유형이 존재한다.

 

① 매크로

C++에서 매크로는 프로그램의 코드 조각 중 일부 이름이 지정된다. 컴파일러가 이 이름을 대면, 컴파일러는 실제 코드 조각으로 이름을 바꾼다. 매크로를 정의하는 데는 #defin 명령이 사용된다.

 

Macro 정의 구문:

#define token value

전처리 후, 토큰이 프로그램에서 해당 값으로 확장된다.

 

매크로 예시:

// C Program to illustrate the macro
#include <stdio.h>

// macro definition
#define LIMIT 5

int main()
{
     for (int i = 0; i < LIMIT; i++) {
          printf("%d \n", i);
     }

     return 0;
}

 

결과:

0             

1             

2             

3             

4             

 

위 프로그램에서 컴파일러가 LIMIT라는 단어를 실행하면 5로 대체한다. 매크로 정의의 'LIMIT'라는 단어를 매크로 템플릿이라고 하고 '5'를 매크로 확장이라고 한다. 참고로, 매크로 정의의 끝에 세미콜론(;)이 없다. 매크로 정의는 끝에 세미콜론이 필요하지 않기 때문이다.

 

또한, C++에는 프로그램에 다양한 기능을 제공하는 데 유용한 몇 가지 사전 정의된 매크로가 존재한다. 그것들 중 하나가 '인수가 있는 매크로'이다. 이것은 인수를 매크로에 전달할 수 있다. 인수로 정의된 매크로는 함수와 유사하게 작동한다.

 

예시:

#define foo(a, b) a + b
#define func(r) r * r

 

이것을 프로그램으로 이해해 보자.

// C Program to illustrate function like macros
#include <stdio.h>
 
// macro with parameter
#define AREA(l, b) (l * b)
 
int main()
{
    int l1 = 10, l2 = 5, area;  
    area = AREA(l1, l2);
    printf("Area of rectangle is: %d", area);
    return 0;
}

 

결과:

Area of rectangle is: 50

 

위의 프로그램을 보면 컴파일러가 프로그램에서 AREA(l, b)를 찾을 때마다 문장(l*b)으로 대체되는 것을 알 수 있다. 이뿐만 아니라 매크로 템플릿 AREA(l, b)로 전달되는 값도 문장(l*b)으로 대체된다. 따라서 AREA(10, 5)는 10*5와 같은 것이다.

 

② 파일 포함

이 유형의 전처리기 지시문은 컴파일러에게 소스 코드 프로그램에 파일을 포함하도록 지시한다. #include preprocessor 지시문은 헤더 파일들을 C 프로그램에 포함시키기 위해 사용된다. 프로그램에 사용자가 포함할 수 있는 파일은 2 가지 유형이 있다. ⑴ 스탠다드 헤더 파일 ⑵ 사용자 정의 헤더 파일이 그 2가지이다.

 

⑴ 스탠다드 헤더 파일

표준 헤더 파일에는 printf(), scanf() 등과 같은 미리 정의된 함수의 정의가 포함되어 있다. 이러한 함수와 함께 작동하려면 이러한 파일이 포함되어야 한다. 다른 함수는 다른 헤더 파일에 선언된다. 예를 들어, 표준 I/O 기능은 'iostream' 파일에 있는 반면, 문자열 작업을 수행하는 기능은 'string' 파일에 있다.

 

구문:

#include <file_name>

여기서 file_name은 포함할 헤더 파일의 이름이다. < 와 > 괄호는 컴파일러가 표준 디렉터리에서 파일을 찾도록 알려주는 것이다.

 

⑵ 사용자 정의 헤더 파일

프로그램이 매우 커지면 작은 파일로 나누어 필요할 때마다 포함하는 것이 좋다. 이러한 유형의 파이은 사용자 정의 헤더 파일인 것이다.

 

구문:

#include "filename"

여기서 따옴표(" ")는 컴파일러에게 소스 파일의 디렉터리에서 헤더 파일을 검색하도록 알려주는 역할을 한다.

 

③ 조건부 컴파일

조건부 컴파일은 프로그램의 특정 부분을 컴파일하거나 일부 조건을 기반으로 프로그램의 특정 부분의 컴파일을 건너뛸 수 있도록 도와주는 것이다. 조건부 코드를 삽입하는 데 사용되는 전처리기 디렉티브는 다음과 같다.

#if Directive
#ifdef Directive
#ifndef Directive
#else Directive
#elif Directive
#endif Directive

#endif 디렉티브는 #if, #ifdef, #ifnedef를 닫는 데 사용되며, 이는 이것들의 전처리가 완료되었음을 의미한다.

 

구문:

#ifdef macro_name                                                                      
    // macro_name이 정의된 경우 실행하는 코드                          
#ifndef macro_name                                                                    
   // macro_name이 정의되지 않은 경우 실행하는 코드               
#if constant_expr                                                                          
    // constant_expression이 true일 경우 실행하는 코드               
#elif another_constant_expr                                                         
    // 다른_constant_expression이 true일 경우 실행하는 코드      
#else                                                                                             
    // 위의 조건 중 어느 것도 사실이 아닐 경우 실행하는 코드      
#endif                                                                                           

 

이름이 'macro_name'인 매크로가 정의되면, 기재한 내용은 정상적으로 실행되겠지만, 정의되지 않으면 컴파일러는 이 블록을 건너뛸 것이다.

 

아래 예시는 #if, #elif, #elif, #else, #endif 전처리기 지침을 포함하는 #의 사용을 보여준다.

//program to demonstrates the use of #if, #elif, #else,
// and #endif preprocessor directives.
#include <stdio.h>

// defining PI
#define PI 3.14159

int main()
{

#ifdef PI
     printf("PI is defined\n");
#elif defined(SQUARE)
     printf("Square is defined\n");
#else
     #error "Neither PI nor SQUARE is defined"
#endif
#ifndef SQUARE
     printf("Square is not defined"); 
#else
     cout << "Square is defined" << endl;
#endif

     return 0;
}

 

결과:

PI is defined              
Square is not defined

 

④ 기타 지시사항

위에서 다룬 지침 외에도, 일반적으로는 사용되지 않는 2가지 지침이 더 있다. ⑴ #undef Directive ⑵ #pragma Directive가 그 2가지이다.

 

 #undef Directive

#undef 지침은 기존 매크로의 정의를 해제하는 데 사용된다. 이 지침은 다음과 같이 작동한다.

#undef LIMIT

이것을 사용하면 기존 매크로 LIMIT가 정의되지 않는다. 따라서 이 내용이 끝나면 모든 "#ifdef LIMIT" 문은 거짓으로 판단될 것이다.

 

예시:

#include <stdio.h>
// defining MIN_VALUE
#define MIN_VALUE 10
int main() {
     // Undefining and redefining MIN_VALUE
     printf("Min value is: %d\n",MIN_VALUE);

 

//undefining max value 
#undef MIN_VALUE

 

// again redefining MIN_VALUE 
#define MIN_VALUE 20

     printf("Min value after undef and again redefining it: %d\n", MIN_VALUE);

     return 0;
}

 

결과:

Min value is: 10                                                  
Min value after undef and again redefining it: 20

 

⑵ #pragma Directive

이 지침은 특수 목적 지침이며 일부 기능을 켜거나 끄는 데 사용된다. 그렇기에 이러한 유형의 지침은 컴파일러마다 다를 수밖에 없다.

 

구문:

#pragma directive


#pragma startup: 이러한 지침은 프로그램을 시작하기 전에(컨트롤이 main()으로 넘어가기 전에) 실행하는 데 필요한 기능을 지정하는 데 도움이 된다.
#pragma exit: 이러한 지침은 프로그램 종료 직전(제어가 main()에서 돌아오기 직전)에 실행하는 데 필요한 기능을 지정하는 데 도움이 된다.

 

예시:

// C program to illustrate the #pragma exit and pragma
// startup
#include <stdio.h>

void func1();
void func2();

// specifying funct1 to execute at start
#pragma startup func1
// specifying funct2 to execute before end
#pragma exit func2

void func1() { printf("Inside func1()\n"); }

void func2() { printf("Inside func2()\n"); }

// driver code
int main()
{
     void func1();
     void func2();
     printf("Inside main()\n");

     return 0;
}

 

예상되는 결과:

Inside func1()
Inside main() 
Inside func2()

 

위의 코드는 GCC 컴파일러에서 실행될 때 다음과 같이 출력을 생성한다.

Inside main()c

이것은 GCC가 #pragma 시작 또는 종료를 지원하지 않기 때문에 발생하는 것이다. 그러나 GCC 컴파일러에서 예상되는 출력은 아래 코드를 사용할 수 있다.

 

#include <stdio.h>

void func1();
void func2();

void __attribute__((constructor)) func1();
void __attribute__((destructor)) func2();

void func1()
{
     printf("Inside func1()\n");
}

void func2()
{
     printf("Inside func2()\n");
}

int main()
{
     printf("Inside main()\n");

     return 0;
}

 

결과:

Inside func1()
Inside main() 
Inside func2()

위 프로그램에서 우리는 몇 가지 특정 구문을 사용하여, 함수 중 하나는 주 함수 이전에 실행하고, 다른 하나는 주 함수 이후에 실행하도록 한 것이다.

 

#pragma warn Directive: 이 지침은 컴파일 중에 표시되는 경고 메시지를 숨기기 위해 사용된다. 아래와 같이 경고를 숨길 수 있다.

#pragma warning -rvl: 값을 반환해야 하는 함수가 값을 반환하지 않을 때 발생하는 경고를 숨긴다.
#pragma warness -par: 이 지침은 함수에 전달된 매개 변수를 사용하지 않을 때 발생하는 경고를 숨긴다.
#pragma warning -rch: 이 명령어는 코드에 도달할 수 없을 때 발생하는 경고를 숨긴다. 예를 들어, 함수의 반환문 뒤에 쓰여진 모든 코드는 도달할 수 없다.

 

728x90