6-4.전처리기

6-4-가.#include

앞에서 함수의 원형을 헤더 파일에 작성하고 #include문으로 이 헤더 파일을 자신의 소스에 포함시키는 실습을 해 보았다. #include와 같은 명령을 전처리기(PreProcessor)라고 하며 이 외에도 여러 가지 종류의 전처리기가 있다. 여기서는 실습에 당장 필요한 #include와 #define에 대해서만 알아보며 #pragma, #ifdef같은 고급 전처리기들은 차후에 다시 다루게 될 것이다.

전처리기는 말 그대로 "앞서 먼저 처리하는 명령"이라는 뜻인데 컴파일하기 전에 소스를 재작성하는 역할을 한다. 컴파일러가 소스를 읽기 전에 전처리기가 먼저 실행되어 컴파일하기 좋도록 소스의 모양을 정리하는데 전처리기는 코드를 생성하지 않으며 어디까지나 소스를 재구성할 뿐이다. 컴파일 전 단계에서 실행되기 때문에 다소 독특한 제약이 있는데 전처리문은 반드시 한 행을 모두 차지해야 하며 전처리문 뒤에 C 코드를 같이 쓸 수 없다. 프리 포맷의 예외인 셈이다. 단 주석은 코드가 아니므로 전처리문 뒤에 올 수 있다.

 

#include <stdio.h> int i;         // 요렇게 할 수 없다. 단 주석은 가능하다.

 

이미 앞에서 배웠던 #include 명령을 보자. 이 명령은 괄호안의 파일을 읽어와 현재 위치에 삽입하는 역할을 한다. #include <stdio.h> 명령에 의해 이 자리에 stdio.h 파일의 내용이 소스에 삽입된다. 이 책에서는 stdio.h 대신 주로 Turboc.h를 포함하는데 두 파일 모두 사용하는 목적은 같으므로 당분간 Turboc.h가 stdio.h라고 생각하도록 하자. 마치 #include 명령이 있는 자리에 stdio.h의 모든 내용을 직접 입력해 넣은 것처럼 메모리에서 소스를 재작성하는 것이다.

 

컴파일러는 #include 명령을 먼저 처리하여 헤더 파일을 모두 포함한 후에 컴파일을 시작한다. 그래서 #include <stdio.h> 명령을 소스 선두에 작성해 놓으면 stdio.h에 선언된 함수의 원형, 데이터 타입, 열거 상수 등을 공짜로 사용할 수 있는 것이다.

#include 명령을 쓰는 대신 헤더 파일의 내용을 복사하여 직접 소스에 붙여 넣어도 결과는 동일하지만 불편할 것이다. #include 명령으로 포함시키면 하나의 헤더 파일을 여러 소스에서 동시에 사용할 수 있으며 소스의 길이가 짧아져 관리하기에도 좋다. #include 다음에 포함할 파일의 이름을 적는데 사용하는 괄호에 따라 두가지 형태가 있다.

 

#include <file.h> : C에서 제공하는 표준 헤더파일을 포함시키고자 할 때 < > 괄호를 사용한다. 컴파일러의 옵션중에 표준 헤더 파일들이 어떤 디렉토리에 있는지를 기억하는 옵션이 있는데 비주얼 C++의 경우 도구/옵션/프로젝트/VC++ 디렉토리(6.0의 경우 Tools/Options/Directories) 대화상자에서 헤더 파일 디렉토리를 지정한다. < >괄호를 사용하면 표준 헤더 파일 디렉토리에서 지정한 파일을 찾는다.

#include "file.h" : 사용자가 직접 작성한 헤더 파일을 포함시키고자 할 때 " "괄호를 사용한다. 이 괄호를 사용하면 소스 파일과 같은 디렉토리에서 헤더 파일을 먼저 찾아 본다. 직접 만든 헤더 파일은 보통 소스와 같은 디렉토리에 두므로 이 괄호를 사용하면 된다.

 

괄호 형식에 따라 헤더 파일을 어디서 먼저 찾을 것인가의 검색 순서가 달라지는데 사실 두 괄호는 그다지 엄격하게 구분할 필요가 없다. " " 괄호를 사용했더라도 현재 디렉토리에 이 파일이 없으면 표준 헤더 파일 디렉토리도 검색하며 반대로 < > 괄호를 사용했더라도 현재 디렉토리도 같이 검색한다. 즉 #include "stdio.h"라고 할 경우 현재 디렉토리에 stdio.h가 있는지 보고 없다면 표준 헤더 파일 디렉토리도 검색하므로 결과는 마찬가지다.

괄호에 따라 결과가 달라지는 경우는 표준 헤더 파일 디렉토리와 현재 디렉토리에 같은 이름을 가지는 헤더 파일이 있을 경우인데 어떤 파일이 우선적으로 포함되는가만 다를 뿐이다. 이런 특수한 경우가 아니라면 굳이 괄호를 구분할 필요는 없다. 그러나 관행상 표준 헤더 파일은 < > 괄호를, 사용자 정의 헤더 파일은 " "를 사용하고 있으므로 이 관행을 지키는 것이 바람직하다.

#include 명령은 주로 헤더 파일을 포함시키기 위해 사용하지만 꼭 헤더 파일만 가능한 것은 아니다. 확장자가 cpp인 파일도 포함할 수 있으며 txt나 임의의 파일이라도 텍스트 파일이기만 하면 다 포함할 수 있다. 예를 들어 1000줄쯤 되는 아주 큰 배열 정의문이 있는데 이 정의문이 너무 길어 소스를 편집하기가 불편하다면 이 부분만 array.cpp로(또는 array.txt, array.inc) 따로 떼어 내고 주 파일에서는 #include "array.cpp"로 불러 오면 된다.

포함할 파일이 주 파일과 다른 디렉토리에 있다면 디렉토리 경로를 사용하는 것도 가능하다. 예를 들어 header.h 파일이 주 파일과 같은 레벨의 include라는 별도의 디렉토리에 저장되어 있다거나 주 파일의 부모 디렉토리 아래의 poham 디렉토리에 있다면 다음과 같은 형식으로 포함시키면 된다.

도스나 윈도우즈에서의 일반적인 상대 경로 지정법과 동일하다. 필요하다면 절대 경로를 줄 수도 있는데 여러 모로 볼 때 바람직하지 않다. 만약 절대 경로의 파일을 포함한다면 이 프로젝트는 다른 컴퓨터로 가져가서 컴파일할 때 디렉토리 구조를 똑같이 만들어야 한다는 제약이 있어 프로젝트 관리가 번거로와진다. 절대 경로에 있는 파일이 꼭 필요하다면 이 파일을 프로젝트 디렉토리로 복사한 후 포함시키거나 아니면 프로젝트 디렉토리 근처에 두고 상대 경로로 지정하는 것이 더 좋다.

윈도우즈 환경에서 디렉토리 경로를 구분할 때는 역슬레쉬(\) 기호를 사용하지만 #include 문에서는 경로 구분자로 슬레쉬(/)를 사용한다. 원래 C언어가 유닉스에서 만들어진 것이기 때문에 유닉스의 디렉토리 구분자인 슬레쉬를 사용하도록 되어 있다. 비주얼 C++은 슬레쉬와 역슬레쉬 모두 인정하는데 가급적이면 표준에 맞게 슬레쉬를 쓰는 것이 이식성에 유리하다. 문자열 상수내에서 \는 확장열이므로 \\로 써야 하지만 헤더 파일의 경로를 표기할 때는 한 번만 써도 상관없다. 사실 헤더 파일명을 지정하는 표현식은 전처리 단계에서 실행되어 컴파일 단계에서는 존재하지 않으므로 문자열 상수가 아니다.

#include 다음의 파일명은 대소문자를 구분하지 않는다. C언어 자체는 대소문자를 구분하지만 윈도우즈의 파일 시스템이 대소문자를 구분하지 않기 때문에 #include <STDIO.H>라고 쓸 수도 있고 #include <stdio.h>라고 해도 문제가 없다. 물론 유닉스나 리눅스 환경에서는 대소문자가 구분되므로 가급적이면 원래 파일명과 똑같이 쓰는 것이 좋다.

#include 명령은 중첩 가능하다. 포함한 파일이 다른 파일을 포함하고 있다면 포함된 모든 파일이 주 파일로 읽혀진다. 예를 들어 A가 B를 포함하고 있는 상태에서 주 파일이 #include "A" 명령을 사용하면 A가 B까지 같이 가지고 주 파일에 포함된다. 이 책에서 편의상 사용하고 있는 Turboc.h 파일의 선두를 보면 다음과 같은 #include 문을 볼 수 있다.

 

#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

#include <time.h>

#include <windows.h>

 

이 파일이 stdio.h, conio.h 등의 표준 헤더를 다 포함하고 있기 때문에 Turboc.h만 포함하면 다른 헤더 파일까지 같이 포함되는 효과가 있으며 그래서 #include <Turboc.h> 명령에 의해 printf, getch 같은 함수들의 원형이 모두 선언되는 것이다.