통합검색
· 마을서비스란?  · 포럼마을  · 일반마을  · 테마마을  · 마을랭킹  · 활동왕
· 덱스퍼트란?  · TECBOX   · PRSBOX   · 이용안내  
· DEXT제품군  · 솔루션베이  · S/W & ESD 컴포넌트
· 프로그램베이
· LiveSeminar  · LiveConference
Visual C++ 포럼마을 입니다.
  마을등급 Visual C++   이 마을은 포럼마을 입니다이 마을은 자유가입제 마을 입니다 마을소개 페이지로 이동 전입신청
마을촌장촌장 나성훈 주민 1573 since 2006-12-29
우리마을 공지사항
질문&답변
강좌&팁
자유게시판
자료실
앨범
개인게시판
마을 게시판
등록된 마을 게시판이
없습니다.
랑데브 게시판
칼럼 게시판
개발자 고충상담
Dev Talk
자유토론방
벼룩시장
재나미 우스개
구인/프로젝트 정보
사람인 채용 게시판
  고객지원 게시판
마이 데브피아
 나의 e-Money 내역
 활동왕 My Page
 스크랩한 게시글보기
 쪽지관리
 주소록관리

 강좌&팁
 유용한 전처리기 살펴보기   | VC++ 일반 2017-03-15 오후 8:49:20
 kyh2984@hotmail.com  kyh2984@hotmail.com님께 메시지 보내기kyh2984@hotmail.com님을 내 주소록에 추가합니다.kyh2984@hotmail.com님의 개인게시판 가기 번호: 8871 추천:0  / 읽음:3,358

전처리기를 모르는 개발자는 없을 것이다. 그러나 주변을 둘러보면 유용한 전처리기를 전체적으로 파악하고 있다가 전처리 지시자를 적시적소에 마법과 같이 사용하는 개발자는 의외로 드문 것을 알 수 있다. 대부분 #include, #define, #if, #ifndef, #endif 정도만 빈번하게 사용하는 편인데, 전처리기라는 것 자체가 비즈니스 로직과는 관계가 없고, C++ 코드를 더욱 복잡하고 지저분하게 만들어 줄 뿐 아니라, 매크로의 경우 가독성이 떨어지고 디버깅이 힘들며 이런 것들이 공동 작업이나 인수인계시 단점으로 작용하기 때문이다. 하지만 C++을 잘 사용한다는 개발자라면 언어가 제공하는 기능이 어떤 것이 있는가 쯤은 한번쯤 전체적으로 정리를 해볼 필요가 있을 뿐만 아니라, 전처리기를 적절하게 사용할 경우 잘 사용한다면 C++ 문법만으론 구현하기 어려울법한 마법과 같은 결과를 얻어 낼 수 있다. 지네와 식물의 독은 멀리해야 인생이 순탄하지만, 보톡스와 같이 사용법을 정확히 알고 올바른 상황에서 적절히 사용한다면 인생이 즐겁다.

 

전처리기의 특징

전처리기는 #으로 시작된다. ANSI C와 같이 일반적으로 사용되는 컴파일러의 경우 #앞쪽에 공백을 허용하지만 초기 C언어의 경우 공백 없이 사용되기도 한다. 전처리기는 C언어에서 독립적이다. 전처리기의 동작은 컴파일러 진입 이전에 끝나기 때문에 C언어의 문법과 키워드에 영향을 받지 않는다. 따라서 <리스트 1>과 같이 C언어의 do 와 같은 키워드도 전처리기에서 재정의 할 수 있다.

 

<리스트 1> do 키워드를 재 정의 하는 #define의 예

void foo() {

#define do /* nothing */

    do {

        std::cout << "Hello world" << std::endl;

    } while (true);

}

 

전처리기는 컴파일러와 다른 계층에서 동작하기 때문에 키워드 재정의 등의 작업에도 컴파일러의 결과나 C언어 문법의 영향을 전혀 받지 않는다. 전처리기의 장점과 단점은 모두 여기에서 시작된다.

 

컴파일러의 전처리기 지원

전처리기를 사용한 코드는 디버깅하기 어려운 것이 사실이다. 그 이유는 컴파일러 전단계에서 동작하므로 컴파일러를 통한 아무런 경고나 에러 메세지를 받을 수 없고 디버깅시 코드를 따라가면서 코드의 흐름을 보기에 불편하기 때문이다. 최신 컴파일러의 경우 잘못된 매크로에 대한 경고 메세지를 보여주거나 매크로를 인라인 함수로 대체하는 식의 선 처리작업과 함께 다양한 디버깅 지원을 해주어 개발자의 커다란 불편함이 조금은 줄어들고 있다.

전처리기는 C언어의 표준의 부분이므로, 벤더와 관계없이 공통적으로 사용할 수 있다. 그러나 일부 전처리기의 경우 사소한 사용법이나 벤더별로 추가적으로 사용할 수 있는 전처리기가 있을 수 있다. 특히 마이크로소프트는 기본적으로 제공해주는 전처리기 이외 편리하게 사용할 수 있는 몇 가지의 추가적인 확장을 통해 개발자의 편의성을 높여주고 있다.

일반적으로 전처리기를 사용하면 전처리기를 검증하기는 어려운 편에 속한다. 특히 검증된 매크로와 같은 전처리기를 사용하는 것이 아니라면 문제가 발생하고서야 잘못된 점을 파악할 수 있기 때문이다.

전처리기는 컴파일러 전단계에서 처리가 된다. 따라서 전처리기를 올바르게 작성했는지 보고 싶다면 전처리기가 적용되어 컴파일러에 진입하기 전단계의 소스코드를 조회하는 것이 편리하다. 대부분의 컴파일러의 경우 전처리기가 적용된 코드를 조회할 수 있는 옵션을 가지고 있다. GCC의 경우 -E 옵션을 통하여 조회하는데, 해당 소스코드에 어떤식으로 전처리기가 적용되었는지 콘솔 화면에서 확인할 수 있다.

 

g++ -E file.cpp

 

-E 옵션을 이용해 전처리기가 적용된 코드를 조회할 경우 컴파일이 되지 않음에 주의한다.

비주얼 C++의 경우 프로젝트 옵션 > Configuration Properties > C/C++ > Preprocessor > Preprocess to a File을 예(/P)로 변경한다.

 

 

 

 

 

 

 

<화면 1> 전처리 결과를 파일에 쓰는 옵션 /P를 선택한 모습

 

 

 

테스트를 위해 <리스트 2>와 같이 전처리기가 포함된 간단한 소스코드를 작성하고 컨트롤 F7을 눌러 파일 단위 컴파일과 같은 동작을 한다.

 

 

 

<리스트 2> 매크로가 포함된 소스코드의 예

 

#include <iostream>

 

#define multiply( f1, f2 ) ( f1 * f2 )

 

#define PRINTOUT(n) std::cout << #n << " has value " << (n) << std::endl

 

int _tmain(int argc, _TCHAR* argv[]) {

 

    int a = 7;

 

    int b = 8;

 

    int c = multiply(7, 8);

 

    PRINTOUT(c);

 

    return 0;

 

}

 

 

 

빌드가 성공적으로 이루어 졌다면 해당파일의 오브젝트파일이 있는 경로를 확인해 소스 파일과 동일한 파일명의 확장자가 .i인 파일을 연다. #include, #define등의 전처리기 내용이 풀어져 거대한 소스코드로 되어 있음을 확인할 수 있다. 일반적으로 전처리된 결과파일의 마지막을 살펴 보면 전처리 이전에 작성된 소스코드를 확인할 수 있다. <리스트 3>은 작성한 매크로가 해석되어 생성된 소스코드의 예를 보여주고 있다.

 

 

 

<리스트 3> 전처리 결과 생성된 소스코드의 예

 

int wmain(int argc, _TCHAR* argv[]) {

 

    int a = 7;

 

    int b = 8;

 

    int c = (7 * 8);

 

    std::cout << "c" << " has value " << (c) << std::endl;

 

    return 0;

 

}

 

 

 

만일 디버깅을 위해 전처리 결과에 주석을 삭제하고 싶지 않다면 컴파일 옵션 /C를 추가한다. 비주얼 C++도 마찬가지로 콘솔창을 통해 결과를 출력할 수도 있다. 컴파일 옵션 /E를 cl.exe에 부여 하는데, 리눅스상에서 개발 습관대로 -E를 입력해도 출력에는 관계 없다.

 

 

 

cl.exe -E cppTest.cpp

 

 

 

전처리 결과에 삽입된 #line 전처리 키워드가 눈에 거슬린다면 /EP 옵션을 부여한다. 정확하게 일치하진 않지만 /EP 옵션으로 생성한 전처리 결과는 GCC의 -E 옵션과 가장 비슷한 결과를 얻을 수 있는 옵션이다.

 

GCC의 -E옵션과 같이 비주얼 C++ 또한 전처리 결과를 생성하는 옵션을 부여할 경우 컴파일이 진행되지 않아 obj 파일들이 생성되지 않음에 주의한다.

 

 

 

빈번히 사용되는 전처리 지시자들

 

전처리기가 #으로 시작됨은 알고 있을 것이다. 만일 #을 단독으로 사용하면 어떻게 될까. 아마도 컴파일 에러나 워닝을 기대하는 개발자가 많겠지만, 단독으로 사용되는 #은 아무런 동작을 하지 않으며 아무런 의미도 없으며 컴파일 에러나 워닝을 던지지도 않는다. 단독으로 사용되는 #은 널 지시문(Null Preprocessor Directive)으로 소스코드상에서 의미 없는 빈칸처럼 취급된다.

 

전처리기중 가장 기본적인 것은 아마도 #include일 것이다. #include는 지정된 파일의 내용을 가져와 파일의 내용을 위치시키는 전처리기 지시문으로 아래와 같이 사용된다.

 

 

 

#include "파일경로"

 

#include <파일경로>

 

 

 

따옴표 양식은 #include 전처리 지시문을 사용한 파일과 동일한 폴더 -> 최근 따옴표양식의 #include 지시자를 이용해 포함한 파일 -> 컴파일 옵션 /I를 이용해 포함한 폴더 -> INCLUDE 환경변수에 지정된 폴더 순으로 검색을 하고 꺽쇠 괄효양식은 /I와 INCLUDE 환경변수 순으로 검색을 한다. 컴파일 옵션 /I를 이용한 폴더 지정은 <화면 2>의 프로젝트 속성에서 디렉토리 옵션과 동일하며 GCC도 옵션이 동일하다.

 

 

<화면 2> 프로젝트 속성 중 디렉토리를 지정하는 옵션

 

 

 

마지막으로 환경변수 INCLUDE에 등록된 폴더를 검색하는데, 프로젝트의 성격과 관리 방법에 따라 컴파일 옵션에 폴더를 추가하는 방법과 환경변수에 폴더를 추가 하는 방법을 혼합하여 사용할 수 있다.

 

 

<화면 3> 환경변수 INCLUDE에 폴더를 등록

 

 

 

환경변수를 이용하는 방법은 배치파일을 통해 빌드 환경을 공유할 경우 유용하게 사용할 수 있는데, 환경변수를 배치파일 환경에서 SET을 이용해 쉽게 설정할 수 있기 때문이다. /I를 이용해 커맨드 라인에서 컴파일 할 경우 아래와 같이 복잡해 질 수 있는 명령어를 만들어 내게 된다.

 

 

 

cl.exe /ID:\MyLibrary\UniComm\include cppTest.cpp

 

 

 

하지만 환경변수를 이용할 경우 검색 폴더경로를 cl.exe의 인자에서 분리할 수 있는 장점이 있다.

 

 

 

SET INCLUDE=D:\MyLibrary\UniComm\include

 

cl.exe cppTest.cpp

 

 

 

#include 지시자를 이용해 보통 헤더파일을 지정하기 때문에 #include를 헤더파일을 포함시키는 지시자로 생각할 수도 있다. 그러나 프로젝트의 상황에 따라 #include에 .cpp파일을 직접 포함하면 문제를 간단하게 해결할 수 있는 상황이 있다. 엄밀히 .h 인가 .cpp인가의 차이는 개발자의 관점에서 헤더파일과 소스파일로 구분하는 것이지 C 컴파일러 입장에서는 차이점이 없다. 흔히 .h파일에도 함수 구현부를 위치시키고, .cpp파일에도 함수 정의부를 포함한다. 비주얼 스튜디오에서는 개발환경상의 편의 제공을 위하여 파일별로 파일 속성을 통해 컴파일러가 사용할 파일인지 헤더 파일인지 또는 그 외의 파일인지 구분한다. 다만 확장자가 .h의 경우 헤더파일로, .cpp의 경우 컴파일러 파일로 분류할 뿐이다.

 

 

<화면 4> 파일 속성에서 해당 파일의 타입을 선택한다.

 

 

 

따라서 다음과 같이 텍스트 파일등을 포함해 코드 구현방법도 생각해 볼 수 있다. 포함할 수 있는 방식에는 제한이 없다. #include를 이용해 해당내용을 포함하였을 때 정상적으로 컴파일이 될 수만 있다면 문제 없다.

 

 

 

<리스트 4> .txt파일 사용의 예

 

// string.txt

 

"Hello World"

 

// foo.cpp

 

void foo() {

 

    std::cout <<

 

#include "string.txt"

 

        << std::endl;

 

}

 

 

 

#define은 매크로를 생성하는 전처리기라는 것을 모르는 개발자는 없을 것이다. #define을 이용하여 상수와 비슷한 오브젝트 형태의 매크로를 생성할 수도 있고, 함수와 유사한 함수형태의 매크로를 선언할 수도 있다.

 

 

 

#define BUFFER_SIZE 1024

 

 

 

이와 같이 오브젝트 형태 중 상수의 의미로 사용하는 매크로는 C++의 const 키워드에 비해 몇 가지 단점이 더 있기 때문에 권장되지 않지만, 아직도 많은 오픈 소스의 경우 많은 수의 상수 의미의 오브젝트 형태 매크로가 사용되고 있다. #define은 소스코드의 특정 부분을 대체 하므로 단순하지 않은 형태의 정의에도 유용하게 사용할 수 있다.

 

 

 

<리스트 5> 배열의 초기화 작업에 매크로가 사용되는 예

 

#define NUMBERS 1, \

 

    2, \

 

    3

 

// ...

 

int x[] = { NUMBERS };

 

 

 

#define으로 선언한 매크로는 #undef를 통해 해제할 수 있다.

 

 

 

<리스트 6> #define과 #undef를 디버그용도로 사용하는 예

 

foo = X;

 

//...

 

#define X 4

 

bar = X;

 

//...

 

#undef X

 

 

 

매크로의 선언과 해제는 컴파일 옵션을 통해서도 할 수 있는데, 컴파일 옵션 /D를 이용하면 매크로를 선언할 수 있다.

 

 

 

cl.exe /DFOO cppTest.cpp

 

 

 

이와 같이 컴파일된 소스 코드는 FOO가 1로 선언된 것과 같은 의미를 가진다.

 

 

 

#define FOO 1

 

 

 

값을 지정하기 위해서 등호를 이용할 수 있다.

 

 

 

cl.exe /DFOO=7 cppTest.cpp

 

 

 

이는 파일 속성의 Preprocessor Definitions에 입력한 것과 동일하다. <화면 5>에서는 파일 속성에서 FOO 매크로에 특정 값을 설정한 예를 나타낸다.

 

 

<화면 5> FOO 매크로 값을 7로 설정한 파일 속성 화면의 예

 

 

 

#undef 또한 컴파일 옵션을 통하여 사용할 수 있는 기능을 제공하는데 /U 컴파일 옵션을 사용한다. /u의 경우 모든 매크로 정의를 해제하며 GCC 또한 -D, -U, -u 컴파일 옵션을 동일하게 제공한다.

 

비주얼C++ 컴파일러의 경우 기본적인 기호를 오브젝트 타입의 매크로 형태로 제공해 주고 있으며 유용하게 사용할 수 있는 기호를 <표 1>에 표기했다.

 

 

 

<표 1> 유용하게 사용할 수 있는 기호

 

기호

설명

_CPPRTTI

RTTI를 사용할 수 있는 /GR옵션의 유무

_CPPUNWIND

CEH(C++ 예외 핸들링) 처리 유무

_MSC_VER

컴파일러의 버전

_WIN32

Win32 응용 프로그램

 

 

 

오브젝트 타입의 매크로는 인자를 받지 않지만 함수 타입의 매크로는 인자를 받는다. 개발자들이 함수라고 알고 있는getc()/putc(),  isalpha()/isupper()등의 is*, toupper()등의 to* 함수들과 stddef.h, stdio.h, ctype.h등의 기본 헤더파일에도 많은 매크로들이 정의되고 있다. 매크로는 소스 코드상에 대체 되기 때문에 인라인 함수를 강제적으로 사용한것과 동일한 효과를 볼 수 있는데다가 구현이 간편하기 때문에 기본적으로 제공되는 간단한 함수들의 매크로 정의는 생각보다 많은 편이다. 비주얼 C++의 경우 디버깅 및 다양한 인라인 함수의 장점을 위하여 이러한 기존의 많은 매크로 구현체들을 인라인 함수화 구현해 놓았다.

 

함수 타입의 매크로의 간단한 예를 <리스트 7>에서 나타내고 있다.

 

 

 

<리스트 7> 함수 타입의 매크로 예

 

#define getrandom(min, max) \

 

    ((rand() % (int)(((max)+1) - (min))) + (min))

 

#define PRINTOUT(n) cout << #n << " has value " << (n) << endl

 

 

 

함수타입의 매크로는 가변 함수처럼 가변 인자를 받을 수 있다. 가변 매크로(Variadic Macros)는 말줄임표를 사용해 가변 인자를 표현하고 __VA_ARGS__가 인자를 받게 된다.

 

 

 

<리스트 8> 가변 매크로의 정의와 사용의 예

 

#define CHECK(...) { printf(__VA_ARGS__); }

 

CHECK(">> %s %s %s", "This", "is", "Variadic Macros\n");

 

 

 

결과 :

 

>> This is Variadic Macros

 

 

 

__VA_ARGS__는 매크로에 인자로 전달되는 쉼포를 포함한 그대로를 대체하고, 다른 인자와 함께 사용할 수 있다.

 

 

 

<리스트 9> 매크로의 예

 

#define CHECK(b, ...) if((b)){ printf(__VA_ARGS__); }

 

CHECK((1 != TRUE), ">> %s %s %s", "This", "is", "Variadic Macros\n");

 

 

 

특별히 사용되는 전처리 지시자들

 

#error는 컴파일 에러를 발생시킬 수 있다. 예를 들어 <리스트 10>과 같은 코드가 있다면 _WINDOWS_ 매크로 상수가 있는지 판단해서 에러 문구를 출력하도록 해 준다. #error는 소스코드의 조건이 맞지 않거나 제약 조건을 위반한 상태를 전처리 과정 수행시 컴파일 에러로 보여 주기에 유용하다.

 

 

 

<리스트 10> #error를 이용한 에러 문구 출력의 예

 

#ifdef _WINDOWS_

 

#error WINDOWS.H already included.

 

#endif

 

 

 

#line은 일반적으로 __LINE__과 함께 사용되며 에러나 경고 메시지등에 안내되는 파일이름과 소스 코드 라인의 행 번호를 변경한다. 일반적인 애플리케이션 개발자라면 소스 코드를 작성하면서 #line을 직접 사용할 기회는 매우 적다. 앞서 <리스트 2> 코드의 전처리 결과를 출력하게 되면 #line이 사용된 소스코드의 일부를 <리스트 11>과 같이 확인할 수 있다.

 

 

 

<리스트 11> <리스트 2>의 전처리 결과 중 #line이 사용된 코드의 일부

 

class __declspec(dllimport) _Winit {

 

public:

 

    __thiscall _Winit();

 

    __thiscall ~_Winit() throw ();

 

private:

 

    static int _Init_cnt;

 

};

 

#line 45 "iostream"

 

}

 

#pragma warning(pop)

 

#pragma pack(pop)

 

#line 6 "cpptest.cpp"

 

 

 

컴파일 시간을 짧게 줄이는 테크닉으로 소개되는 방법가운데 여러 CPP 파일을 하나의 커다란 파일로 만들어 하나의 파일만 빌드 하는 것이 있었다. 목적은 다르지만 전처리 결과로 만들어진 소스코드가 이 테크닉과 동일한 수행을 하는데, 중간에 #line을 통하여 원본 파일의 경로와 행번호를 대체하기 때문에 __LINE__과 연계된 각종 메시지를 통해 올바른 원본경로를 찾아갈 수 있게 된다.

 

#pragma는 컴파일러 작성자에 의해서 정의된 다양한 명령을 컴파일러에게 제공하기 위해 사용되는 지시어이다. 컴파일러의 여러 가지 옵션을 명령행 에서가 아닌 코드에서 직접 설정한다. 각 벤더별로 운영체제나 컴파일러가 제공하는 기능과 제어하는 방법이 다를 수 있기 때문에 일반적으로 컴파일러마다 다른 옵션인 경우가 많다. 또한 운영체제와 컴파일러에 요청할 수 있는 많은 옵션들이 존재하며 새로운 운영체제 혹은 새로운 컴파일러가 나올 때 마다 추가되기도 한다. 이중 개발자들이 알면 도움이 될만한 몇 가지를 소개한다.

 

wcout을 이용해 한글이 포함된 유니코드 문자열을 출력했을 때, 정상적으로 출력이 되지 않는 경우가 있다.

 

 

 

std::wcout << L"abc한글" << std::endl;

 

 

 

예를 들어 “abc한글” 이라는 이라는 결과가 나와야 하는데, "abc"만 출력되는 문제와 같은 것이다. 한글이 출력되지 않는 이유는 로케일이 맞지 않기 때문인데, 로케일에 따라서 멀티바이트와 유니코드간 이를 해결하기 위하여 흔히 setlocale() 함수를 사용하여 의도대로 출력 시킨다. #pragma setlocale을 이용하면 setlocale() 함수를 이용하지 않고 이를 전처리기를 통하여 해결해 줄 수 있다.

 

#pragma message은 컴파일 시 output창에 특정 메시지를 출력해 줄 수 있다. 예를들어 어떤 상수가 선언되어 있지 않아 기능이 제한된 컴파일을 한다면 이러한 내용을 메시지로 알려줄 수 있을 것이다.

 

 

 

<리스트 12> msacm.h에서 사용된 #pragma message

 

#if defined(UNICODE) && !defined(_UNICODE)

 

#ifndef RC_INVOKED

 

#pragma message("MSACM.H: defining _UNICODE

 

because application defined UNICODE")

 

#endif

 

#define _UNICODE

 

#endif

 

 

 

#pragma deprecated는 사용되지 않는 함수나 앞으로 사용하지 않을 함수에 대해서 경고 메시지를 주어 다른 함수를 사용하도록 유도 할 수 있게 한다. #pragma deprecated는 인자로 1개 이상의 식별자를 받는데, 식별자는 함수 이름으로 전처리 과정에서 해당 #pragma deprecated 이후 인식되는 소스코드에서 인자로 넘겨진 함수 이름이 쓰였을 경우 C4995 컴파일 에러를 발생시킨다.

 

 

 

<리스트 13> #pragma deprecated를 이용해 사용하지 않는 함수의 사용을 경고한다.

 

void foo() {

 

    func1();

 

    func2();

 

#pragma deprecated(func1, func2)

 

    func1(); // C4995

 

    func2(); // C4995

 

}

 

 

 

#pragma region [name]/endregion [comment]은 비주얼 스튜디오의 편집기에서 축소 시킬 수 있는 영역을 지정한다. 비주얼 스튜디오는 기본적으로 함수, 주석등의 영역을 축소시킬 수 있도록 하여 개발시 편리한 환경을 제공해 주는데, #pragma region을 통해 임의의 영역에 이름과 주석을 붙여 축소 영역을 만들 수 있다. <화면 6>에는 #pragma region을 이용하여 초기화코드, 환경변수 초기화라는 이름과 주석으로 영역을 축소시킨 모습을 보여주고 있다.

 

 

 

<화면 6> #pragma region/endregion을 이용현 구역 설정

 

 

 

지정된 전처리기 상수인 미리 정의된 매크로(Predefined Macros)

 

ANSI C에는 미리 정의된 매크로가 있다. MFC등 일부 빈번하게 참조되는 소스 코드에 사용되어 눈에 충분히 익은 __DATE__, __FILE__과 같은것이 그것인데, 이들 매크로는 임의로 정의를 해제할 수 없다.

 

 

 

<표 2> ANSI 표준 전처리 상수

 

상수

설명

__DATE__

해당 소스파일을 컴파일한 날짜로 대체

__FILE__

현재 소스파일의 이름으로 대체

__LINE__

현재 소스파일에서의 라인번호 정수 상수로 대체

__STDC__

컴파일러가 표준 ANSI C를 지원하는지 유무

__TIME__

해당 소스파일을 컴파일한 시간으로 대체

__cplusplus

컴파일러가 c++을 지원하는지 유무

 

 

 

__DATE__는 프로젝트의 각 파일들이 컴파일 될 때 해당 소스 파일이 컴파일 되는 날짜 문자열로 대체되는데, 컴파일러 내부적으로 time.h의 asctime() 함수를 이용하여 Mmm dd yyyy와 같은 양식이 사용된다.

 

__FILE__은 현재 소스파일의 이름으로 대체 된다. 컴파일 옵션 상태에 따라 소스 파일 이름이 전체 경로가 포함된 파일이름으로 대체되기도 하지만 파일이름만으로 대체 되기도 한다. 예를 들어 일반적인 디버그(Debug)모드에서는 전체 경로가 표기가 되고 릴리즈(Release)모드에서는 파일 이름이 표기가 된다. 파일 속성에서 Configuration Properties > C/C++ > Advanced의 Use Full Paths 혹은 파일 이름을 전체 경로로 표시해 주는 옵션인 /FC를 부여할 경우 릴리즈 모드와 같은 컴파일 옵션에서도 전체 경로를 확인할 수 있어 컴파일 안내문구를 좀 더 쉽게 파악할 수 있다. 만일 컴파일 에러를 만난다면 보통 파일 이름만 나오는 에러 메세지를 볼 수 있다.

 

 

 

compiler_option_FC.cpp(5) : error C2143: syntax error : missing ';' before '}'

 

 

 

만일 /FC 옵션을 적용했다면 파일이름에 전체 경로를 사용하여 안내 문구를 좀 더 쉽게 파악할 수 있다.

 

 

 

c:\test\compiler_option_FC.cpp(5) : error C2143: syntax error : missing ';' before '}'

 

 

 

전체 경로로 메세지를 받게 되면 비슷한 파일 이름이 있거나 옵션에 따라 특정 경로의 구현 파일을 사용하는 경우 문제발생시 상황판단에 도움을 받는다. /FC 옵션은 __FILE__ 전처리기 상수에도 영향을 주는데, __FILE__로 대체되는 파일 이름에 전체경로를 포함하게 된다. 참고로 디버그 모드와 릴리즈모드에서 전체경로 포함유무가 다르게 나오는 이유는 생성하는 디버그 정보 종류가 다르기 때문이다. 파일 속성에서 Configuration Properties > C/C++ > General의 Debug Information Format의 기본설정이 디버그 모드에서는 Program Database for Edit And Continue (/ZI)로 되어 있고, 릴리즈 모드에서는 Program Database(/Zi)로 되어 있음을 확인할 수 있는데, 이 차이에 따라 전체 경로의 표기 유무가 달라지게 된다.

 

 

 

<리스트 14> 유니코드 스트링을 반환하는 __WFILE__ 구현의 예

 

#define WIDEN2(x) L ## x

 

#define WIDEN(x) WIDEN2(x)

 

#define __WFILE__ WIDEN(__FILE__)

 

std::basic_string<wchar_t> wideStr = __WFILE__;

 

 

 

__cplusplus는 해당 컴파일러가 C++을 지원하는지 여부를 정수 값으로 대체 되는데, C++ 표준을 모두 지원한다면 199711 이상의 값으로 대체 된다. <리스트 15>는 미리정의된 매크로의 각 값들을 출력하는 함수와 결과값의 예를 보여준다.

 

 

 

<리스트 15> 매크로의 예

 

string GetInformation()

 

{

 

    stringstream result;

 

    result << "This is the line number " << __LINE__;

 

    result << " of file " << __FILE__ << ".\n";

 

    result << "Its compilation began " << __DATE__;

 

    result << " at " << __TIME__ << ".\n";

 

    result << "The compiler gives a __cplusplus value of " << __cplusplus;

 

    return result.str();

 

}

 

// 결과 :

 

This is the line number 31 of file cpptest.cpp.

 

Its compilation began Jan 1 2013 at 10:59:42.

 

The compiler gives a __cplusplus value of 199711

 

 

 

.cpp 파일 단위로 적용되는 옵션

 

전처리기 옵션의 경우 프로젝트 단위로도 적용할 수 있지만 .cpp 파일 단위로 적용이 가능하다. 이렇게 일부 옵션의 경우 프로젝트 단위가 아닌 파일 단위로 적용할 수 있는 옵션들이 있다. 대표적인것이 미리컴파일된 헤더(Precompiled Header) 사용 옵션이다. 미리컴파일된 헤더를 잘 사용하는 개발자라면 문제가 되지 않지만 미리컴파일된 헤더를 잘 사용하지 않는 개발자의 경우 현재 프로젝트에서는 미리컴파일된 헤더 사용 옵션이 켜진채 진행되고 있는데, 미리컴파일된 헤더를 사용하지 않던 프로젝트에서 사용하던 소스 코드를 가져올 경우 관련된 헤더 파일인 stdafx.h를 찾을 수 없다는 에러를 만나게 된다. 이때 가져온 소스코드에 #include "stdafx.h"만 포함하면 간단하게 넘어갈 수 있지만, 문제의 원인을 정확하게 해결하는 방법은 가져온 소스코드의 파일 속성에서 미리컴파일된 헤더를 사용안함으로 변경해 주는 것이다.

 

최적화나 콜링컨벤션 종류, 적용되는 전처리기 상수, OpenMP 사용 유무, 인트린직 함수 사용 유무등 수십여개의 옵션 또한 파일 단위로 적용되는 옵션에 들어가는데, 공동 작업중 이러한 옵션 변경이 필요하지만 이를 파일단위로 적용하지 않고 프로젝트 단위로 적용할 경우 상당한 프로젝트의 수정작업이 필요한 경우가 많다.

 

참고자료

1. GNU The C Preprocessor - http://gcc.gnu.org/onlinedocs/cpp/

2. C/C++ Preprocessor Reference - http://msdn.microsoft.com/en-us/library/y4skk93w.aspx

3. Preprocessor directives - http://www.cplusplus.com/doc/tutorial/preprocessor/

4. Options Controlling the Preprocessor - http://gcc.gnu.org/onlinedocs/gcc/Preprocessor-Options.html

 

 

[코멘트] 좋음
2017-06-11 23:22
 skawndns  skawndns님께 메시지 보내기skawndns님을 내 주소록에 추가합니다.skawndns님의 개인게시판 가기 
오. 글 매우 유용합니다..ㄳ
저장 취소
코멘트쓰기
  좋음   놀람   궁금   화남   슬픔   최고   침묵   시무룩   부끄럼   난감
* 코멘트는 500자 이내(띄어쓰기 포함)로 적어주세요.
목록 보기   지금 보고 계시는 글을 회원님의 my Mblog >> 스크랩에 넣어두고 다음에 바로 보실 수 있습니다.  
회사소개  |   개인정보취급방침  |  제휴문의  |   광고문의  |   E-Mail 무단수집거부  |   고객지원  |   이용안내  |   세금계산서
사업자등록번호 안내: 220-81-90008 / 통신판매업신고번호 제 2017-서울구로-0055호 / 대표: 홍영준, 서민호
08390, 서울시 구로구 디지털로32길 30, 1211호 / TEL. 02_6719_6200 / FAX. 02-6499-1910
Copyright ⓒ (주) 데브피아. All rights reserved.