본문 바로가기

Programming/C C++

[C, C++] Preprocessor(전처리)

C Program이 Compile시에는 실제 Source의 내용을 바로 Compile하는 것이 아니라 일단 개발자가 작성한 Source를 분석하여 필요시 일부 Source를 수정하는 작업을 진행 하게됩니다. 이처럼 Compile하기전에 미리 어떠한 작업이 이루어지는 것을 흔히 '전처리'라고 부릅니다.(전처리 과정은 대게 Source File과 include File을 결합한 직후 발생합니다.)

참고:
C언어는 일반적으로 ; 문자까지를 하나의 행으로 보지만 예외적으로 #로 시작하는 내용은 그 행의 끝까지를 하나의 행으로 간주합니다.

1. #include

Source Code의 처음부분에 #include <header file> 형식으로 선언되어 지정된 Header File을 Program의 Source Code에 포함되도록 합니다. 그러면 전처리를 통해 실제 Header File의 내용을 추가하는 작업이 이루어 지게 되는것입니다.

#include <stdio.h>

main()
{
  printf("hello\n");
}

▶Source 작성

/***
*stdio.h - definitions/declarations for standard I/O routines
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
*       This file defines the structures, values, macros, and functions
*       used by the level 2 I/O ("standard I/O") routines.
*       [ANSI/System V]
*
*       [Public]
*
****/

#if     _MSC_VER > 1000
#pragma once
#endif
#ifndef _INC_STDIO
#define _INC_STDIO
#if     !defined(_WIN32)
#error ERROR: Only Win32 target supported!
#endif
--이하생략... 위 부분이 stdio.h의 실제 내용

main()
{
  printf("hello\n");
}


전처리에 의해 stdio.h Header File이 Program에 추가됩니다.

C에는 많은 Library의 Header File이 존재하지만 이 Header File을 개발자가 직접 작성여여 사용할 수도 있습니다. 이럴때는 #include를 통해 <와 >대신 "문자를 통해 사용자 Header File을 추가하면 됩니다.

#include "header file"

이때 해당 Header File은 현재 작업중인 경로에서 찾게 되는데 지정된 Hearder File이 없는 경우 다시 원래의 표준경로를 찾아가 Header File을 찾게 됩니다. 또한 사용자가 직접 작성한 Hearder File이라 하더라도 현재작업중인 경로가 아닌 표준경로에 저장된 경우 ""대신 <>로 Header File을 지정하여도 정상적으로 동작하게 됩니다.

myprint(char s[])
{
  printf("%s <- 문자열 출력\n", s);
}
▶'myheader.h' 라는 이름으로 Header File작성

#include <stdio.h>
#include "myheader.h"

main()
{
  myprint("hello");
}


myheader.h Header File을 현재 위치에 저장한 후 "로 myheader.h File을 포함합니다.


2. #define

전처리과정에서 #define로 정의된 문자열을 Source상에서 치환되도록 Macro를 정의합니다.

#include <stdio.h>
#define Count 10

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

▶ #define을 통해 Count라는 문자열을 정수 10으로 인식합니다.

#include <stdio.h>

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


전처리를 통해 Source Code에 있는 Count라는 문자열을 10으로 치환한후 Compile합니다.


또한 #define를 사용하면 인수를 가져야 하는 함수자체를 정의할 수도 있어서 특정 함수를 간단히 활용할 수 있습니다.

#include <stdio.h>
#define prnt(s) printf("%s\n", s)

main()
{
  prnt("문자열 출력");
}


printf("%s\n", s)함수를 printf(s)로 Macro정의합니다. 이때 s는 화면에 출력할 문자열의 인수입니다.


Macro는 사용자가 정의할 수도 있지만 편의상 미리 마련된 Macro도 있습니다.

다음은 해당 Macro종류및 사용 예제입니다.

 macro  설명
 __DATE__  현재 날짜를 나타냅니다.(Computer설정 기준)
 __LINE__  _LINE_가 위치한 행의 번호를 나타냅니다.
 __TIME__  현재 시간을 나타냅니다.(Computer설정 기준)
 __FILE__  _FILE_이 있는 Source File의 이름을 나타냅니다.
 __STDC__  현재 C Program이 완벽히 ANSI C 표준만을 따를때 이 Macro가 정의됩니다.(값1)
만일 표준을 따르지 않고 현재 사용중인 개별 Compiler의 확장기능을 이용했을 경우 이 Macro는 정의되지 않습니다.

#include <stdio.h>

main()
{
  printf("현재 날짜는 %s 이며 시간은 %s 입니다.\n", __DATE__, __TIME__);
  printf("이 program file의 이름은 %s 입니다.\n", __FILE__);
  printf("이 행의 번호는 %d 입니다.\n", __LINE__);
  printf("ANSI C 표준입니까?%d\n", __STDC__);
}



Compile할때 /Za 를 지정하였습니다. ANSI C에 맞춰야 __STDC__ Macro를 확인할 수 있기 때문입니다. Microsoft C/C++ Compiler는 Compil시 /Za 가 있을때 해당 Source를 ANSI C에 맞게 Compile합니다.

3. #undef

#define를 통해서는 Macro를 정의하는 반면 #undef는 정의된 Macro를 해제하는 역활을 합니다.(#undef로 해제된 Macro는 #define로 다시 재정의 될 수 있습니다.)

#include <stdio.h>
#define line 500

main()
{
  int i;
  i = line;
  printf("현재 macro : %d\n", i);
 
  #undef line
  #define line 1000
 
  i = line;
 
  printf("재정의된 macro : %d\n", i);
}


main함수에서 #undef를 통해 미리 정의된 Macro를 해제하고 다시 #define로 Macro를 정의합니다.

주의:
#으로 시작하는 전처리는 끝에 ;문자를 붙이지 않습니다.


4. #if ~ #elif / #else ~ #endif

특정 조건을 만족할때만 개발자가 직접 작성한 Source Code의 일부분을 Compil하거나 또는 하지 않을 수 있습니다.(이것을 조건부 Compile이라고 합니다.)

#include <stdio.h>
#define error 0

main()
{
  #if (error == 0)
    printf("error는 0\n");
  #elif (error == 1)
    printf("error는 1\n");
  #else
    printf("이도저도 아님...\n");
  #endif
}


전처리에 의해 error가 정의된 값을 판단하여 해당 값에 따라 Compil하도록 합니다. 이것은 본래 if문과 작동은 비슷한 형태인데 error가 0이면 #if이하의 내용만, error가 1이면 #elif이하의 내용만 Compile하며 error가 0도 1도 아니면 #else이하 Code만을 Compile하도록 합니다.

실제 작동은 보통 if문을 쓸때와 별 다를바가 없지만 여기서는 Compile되는 Program에 어떤 Code의 내용만이 들어가는가가 중요합니다. 만일 #if를 쓰지않고 일반 if문을 쓰게되면 error가 무엇인지에 상관없이 모든 Code가 Compile될 것입니다.

#if는 전처리를 행한다는 특징때문에 Program이 실행되고 있다는 가정하에서는 아무런 소용이 없으며 일반적으로 Program Debug시에만 사용하곤 합니다.

5. #ifdef / #ifndef

Macro는 #define을 통해서 정의합니다. 이때 해당 Macro가 정의되었는가 또는 정의되지않았는가에 따라 별도의 처리를 하고자 한다면 #ifdef / #ifndef를 사용하도록 합니다.

#include <stdio.h>
#define error 10

main()
{
 #ifdef error
  printf("error가 10으로 정의됨\n");
 #endif
 #undef error
 #ifndef error
  printf("error가 정의되지 않음\n");
 #endif
}


#ifdef에 의해 error가 정의되어 있다고 판단되면 printf("error가 10으로 정의됨\n"); 부분을 Compile하게 되고 #undef에 의해 해당 Macro가 취소됩니다. 또한 #ifndef로 Macro미정의가 판단되어 printf("error가 정의되지 않음\n"); 부분이 Compile되는 것입니다.


6. #if ~ defined / !defined

앞서 살펴본 #ifdef는 단일 Macro만을 판단하지만 #if ~ defined(!defined)는 조건지시시자 개념으로 #ifdef를 보다 확장한 것이며 defined는 참일경우 !defined는 거짓일 경우 처리됩니다.

#include <stdio.h>
#define a 1
#define b 2

main()
{
  #if defined(a)
    printf("a 정의됨!\n");
  #endif
 
  #if !defined(c)
    printf("c 정의안됨!\n");
  #endif
 
  #if defined(b)
    printf("b 정의됨!\n");
  #endif
 
  #if defined(a) && defined(b)
    printf("a와 b둘다 정의됨.\n");
  #endif
}


#if ~ defined/!defined 는 위와같이 if조건연산자인 &&이나 ||등을 지정할 수 있습니다.


7. #error

조건에 따라 compile시에 Error Message를 표시하도록 합니다.

#include <stdio.h>
#define a 1

#if a == 1
#error a error...
#endif

main()
{
  printf("aaa\n");
}


#if ~ #endif 에 따라 a가 1이면 #error에 정의된 Message를 출력하도록 하였습니다.


8. ##

##은 이 ##을 기준으로 양쪽의 내용을 결합니다.

#include <stdio.h>
#include <string.h>

#define prnt(a, b) a ## b

main()
{
  printf("%s\n", prnt("hello", "world"));
}


prnt에 두개의 문자열을 , 로 분리하여 전달하지만 #define에서 a ## b문으로 "helloworld"내용으로 결합됩니다.