18-2-나.조건부 컴파일

조건부 컴파일 지시자(Conditional Compile Directive)는 지정한 조건의 진위 여부에 따라 코드의 일정 부분을 컴파일할 것인지 아닌지를 지정한다. 전처리문이므로 컴파일되기 전에 조건을 평가하며 코드를 컴파일 대상에 포함시키거나 제외시키는 역할을 한다. 이때 조건의 형태는 여러 가지가 있지만 주로 매크로 상수의 존재 여부나 값에 대한 평가식이 사용된다. 실행중에 결정되는 변수의 값이나 함수 호출은 당연히 조건문이 될 수 없다.

조건부 컴파일 지시자를 잘 활용하면 한벌의 코드를 조건에 따라 다르게 컴파일하여 상이한 실행 파일을 만들어낼 수 있다. 만약 조건부 컴파일 기능이 없다면 실행 파일별로 소스를 따로 유지해야 하므로 무척 번거로와진다. 조건부 컴파일 지시자는 다양한 상황과 목적에 맞게 소스를 컴파일하여 호환성과 이식성을 확보하는 수단으로 빈번하게 활용되므로 잘 알아 두도록 하자. 다음 구문이 조건부 컴파일문의 가장 전형적인 예이다.

 

#ifdef 매크로명

코드

#endif

 

#ifdef 다음에 조건이 되는 매크로명을 써 주고 #endif 사이에 조건부로 컴파일할 코드를 작성한다. 조건부 컴파일 블록에 포함된 코드는 매크로가 존재하면 컴파일될 것이고 그렇지 않다면 전처리 과정에서 삭제되어 아예 없는 것으로 취급된다. #ifdef ~ #endif 블록으로 조건부 컴파일 대상 코드를 명시하므로 { } 로 이 코드를 감쌀 필요는 없다.

실제 프로젝트에서 조건부 컴파일이 사용되는 예를 들어 보자. 워드 프로세서를 만드는데 전문가용과 일반용 두 버전을 만들고 전문가용은 좀 더 고급스럽고 강력한 기능을 지원하는 대신 가격을 좀 더 비싸게 받으려고 한다. 이런 가격 차별화 정책은 구매자의 경제적 능력에 맞는 다양한 버전을 제공함으로써 최대한의 수익을 올리는 중요한 마케팅 기법이며 소프트웨어 업계에서 흔하게 볼 수 있다.

이 경우 전문가용은 일반용 버전의 기능을 모두 가지고 추가로 고급 기능을 더 가지는 셈이므로 일반용 버전의 코드 전체를 필요로 하며 일반용은 전문가용 버전의 코드 중 일부를 빼야 한다. 대부분의 코드가 중복되므로 두 버전의 프로젝트를 따로 유지하는 것보다는 차이가 나는 부분만 조건부 컴파일하는 것이 훨씬 더 유리하다. 전문가용에만 포함되는 고급 기능은 다음과 같이 조건부 컴파일 블록에 배치한다.

 

#ifdef PROFESSIONAL

고급 기능

#endif

 

전문가용에만 포함되는 코드를 모두 이런 조건부 컴파일 블록에 포함시켜 놓으면 이 코드들은 PROFESSIONAL 매크로 상수가 정의되어 있을 때만 컴파일될 것이다. 소스 선두에 #define PROFESSIONAL이라는 매크로 정의문을 미리 작성해 놓고 이대로 컴파일하면 전문가용 실행 파일이 생성되고 이 정의문을 주석 처리한 후 다시 컴파일하면 고급 기능은 컴파일 대상에서 제외되는 일반용 실행 파일이 생성된다. 매크로 정의문 하나로 전체 코드의 컴파일 범위를 간편하게 통제할 수 있는 것이다.

#ifndef는 #ifdef와 반대의 조건을 점검하는 지시자이다. #ifdef는 매크로가 정의되어 있을 때만 컴파일하지만 #ifndef는 반대로 매크로가 정의되어 있지 않을 때만 컴파일한다. 가령 일반용 버전에만 어떤 코드를 넣고 싶다면 #ifndef PROFESSIONAL ... #endif 블록을 구성하면 된다. 셰어웨어의 데모 버전에 시간 제한 기능을 넣는다거나 실행할 때마다 등록 대화상자를 출력하여 공짜 사용자를 귀찮게 하는 기능들은 보통 조건부 컴파일로 처리한다.

#ifdef ~ #endif 사이에 #else를 넣을 수도 있는데 말 뜻 그대로 #else 이하는 그 외의 조건인 경우를 처리한다. 코드의 포함 여부뿐만 아니라 조건에 따라 코드를 바꿔가며 컴파일하고 싶다면 #ifdef와 #else를 같이 사용한다. 전문가용인 경우와 일반용인 경우 기능을 조금 다르게 작성하고 싶다면 다음과 같이 조건부 블록을 작성한다.

 

#ifdef PROFESSIONAL

전문가용의 코드

#else

일반용의 코드

#endif

 

이렇게 하면 전문가용과 일반용의 기능을 쉽게 차별화할 수 있다. 예를 들어 전문가용에는 자동 저장 기능을 넣어주고 일반용에는 수동 저장 기능을 넣는다거나 동시 편집 가능한 문서의 개수를 다르게 지정할 수 있다. 조건부 컴파일 지시자는 아주 여러 가지 용도로 사용되는데 표준 헤더 파일에도 무수히 많은 #ifdef를 볼 수 있다. 다음은 윈도우즈의 표준 헤더 파일에 있는 조건부 컴파일 블록의 예이다.

 

#ifdef UNICODE

   LPWSTR   pszValue;

#else

   LPSTR    pszValue;

#endif

 

윈도우즈용 프로그램은 ANSI 버전과 UNICODE 버전으로 각각 컴파일할 수 있는데 UNICODE 매크로가 정의되어 있을 때와 그렇지 않을 때의 변수 타입을 다르게 정의한다. 이렇게 하면 한 소스로 유니코드와 안시를 모두 지원할 수 있으며 플랫폼이나 실행 환경에 따라 매크로 정의문만 조정하면 되므로 이식성을 쉽게 확보할 수 있다. 다음 코드는 개발 중에 디버깅을 위해 흔히 사용하는 예이다.

 

#ifdef _DEBUG

printf("변수 값 확인. i=%d\n",i);

#endif

 

관심있는 변수를 화면에 출력하여 실행중에 값을 확인해 보도록 했는데 이 출력문이 조건부 컴파일 블록에 포함되어 있으므로 _DEBUG 매크로가 정의되어 있을 때만 컴파일된다. 즉 이 코드는 개발중에 디버깅 편의를 위해 삽입된 임시 코드이며 실제로 릴리즈할 때는 컴파일하지 말아야 한다. 만약 조건부 컴파일 지시자가 없다면 이런 임시 코드를 일일이 넣었다 뺐다 해야 하므로 무척 불편할 것이다.

조건부 컴파일 지시자의 조건으로 사용되는 매크로는 물론 #define 전처리문으로 정의하며 이 문장 자체는 소스에 작성된다. 조건을 바꾸려면 최소한 이 정의문은 편집해야 하므로 컴파일 조건을 바꾸려면 주석 처리는 수동으로 해야 한다. 소스를 전혀 수정하지 않고 한 벌의 소스로 여러 종류의 실행 파일을 빌드할 수 있다면 더 편리할 것이다. 그래서 대부분의 컴파일러는 소스가 아닌 외부에서 매크로를 정의할 수 있는 방법을 제공한다. 비주얼 C++ 6.0의 프로젝트 설정 대화상자를 보자.

C/C++탭의 General 카테고리의 Preprocessor definitions 란에 컴파일전에 미리 정의할 매크로 상수 목록이 있다. 여기에 원하는 매크로를 적어주면 소스는 건드리지 않고도 조건을 바꿀 수 있다. 또한 비주얼 C++은 이런 설정 상태를 여러 벌 만들고 편집할 수 있으며 배치 빌드까지 지원하므로 한 번 설정만 잘 해 놓으면 원터치로 전문가용, 일반용, 데모 버전, 한국어/영어 버전을 빌드할 수 있다. 명령행 컴파일러들은 컴파일 전에 미리 특정 매크로를 정의하는 옵션을 제공하기도 한다.

 

+ Recent posts