고수들이 절대 가르쳐 주지 않는 C/C++ 프로그래밍 팁 #1 A/S - include guard 관련 이슈

Posted at 2007. 6. 20. 08:30 // in S/W개발/고절가주팁 // by 김윤수


고절가주팁 #1에서 헤더 파일이 중복 포함되는 것을 방지하는 방법을 설명드렸습니다. 이 팁을 공개하고 났더니 여러분들이 댓글을 달아 주시더군요. 그 중에 #pragma once 컴파일러 지시자는 한 번 더 자세히 알아볼 필요가 있을 것 같아서 이글을 쓰게 됐습니다.

일단 Microsoft Visual C++ 와  GCC는 확실히 #pragma once 지시자를 지원하는 것으로 보입니다. 다른 컴파일러에서 제가 #pragma once 를 사용해 본 적이 없어서 지원 여부를 잘 모르겠네요. 혹시 다른 컴파일러 사용하시는 분이 있으시다면 #pragma once 지원 여부를 알려주시면 정말 감사하겠습니다.

알단 제가 제시했던 헤더 파일 중복 포함 방지 기법은 소위, include guards라고 불리더군요(누가 이렇게 이름을 잘 짓나 몰라요 참~). 이 include guards 와 #pragma once 의 가장 큰 차이점은 include guards 는 한 번 읽은 헤더 파일도 일단 헤더 파일의 내용을 다 읽어야 하는 반면, #pragma once 의 경우에는 각 파일별로 프리프로세서가 include 한 상태를 기억하면 되므로 한 번 읽은 헤더 파일은 읽지 않아도 됩니다. 따라서 컴파일 속도 측면에서 보면 #pragma once 가 더 유리합니다.

그리고, include guards 의 경우, 각 헤더 파일에 대응하는 매크로 이름이 서로 충돌할 가능성이 있다는 것입니다. 여러 라이브러리를 섞어 쓰다 보면 우연히 헤더 파일 대응 매크로 이름이 충돌하여 헤더 파일이 포함되어야 함에도 불구하고, 포함되지 않는 경우가 발생할 수 있다는 것입니다. 이런 문제는 대규모 프로젝트라면 항상 발생할 수 있는 문제이지요. 이런 문제가 발생할 가능성을 줄이려면 include guards 명명 규칙을 나름대로 정해서 매크로 이름이 충돌되지 않도록 해야 합니다. 예를 든다면 매크로명을 _MODULE_HEADER_H 와 같은 방식으로 정의할 수 있을 것입니다. 모듈명이 먼저 나온 후에 헤더 파일의 이름을 쓰는 거죠. 예를 들어 dispatcher 모듈의 모듈명 DISP 라고 가정하고, dispatcher.h 파일에 대한 include guards 를 정의한다면 _DISP_DISPATCHER_H 와 같이 명명할 수 있을 것입니다.

#pragma once 도 문제가 없는 건 아닙니다. 프리프로세서에서 #pragma once 를 처리하는 데 버그가 있거나 파일 시스템 상에서 파일의 동일성을 파악하는 데 어려움이 있다거나 하면 문제가 좀 심각해 집니다. 예를 들어, Unix 계열의 OS 에서는 서로 다른 경로명을 가지고 있지만 결국 같은 파일을 가리키는 경우가 있습니다(hard link, soft link). 이렇게 되면 속수 무책으로 헤더 파일을 중복 포함되겠지요. #pragma once 를 썼는데도 중복 포함으로 인한 에러 메시지를 보게 된다면 문제를 찾기가 쉽지 않을 것입니다.

게다가 고절가주팁 #1의 댓글에서 몇 몇 분이 이미 밝혀 주셨지만 #pragma once 는 결정적으로 표준이 아니라는 문제가 있습니다. 컴파일러마다 지원 여부가 다 달라진다는 것이죠. 따라서 #pragma once 를 쓰게 되면 이식성이 좀 떨어지게 됩니다. 이런 이유 때문인지 GCC 에서는 #pragma once 를 obsolete feature 로 규정하고, include guards 형태를 #pragma once 와 거의 동일하게 처리하는 기능을 추가했다고 하더군요(include guard optimization).

마지막으로 한 가지 더 말씀드리고 싶은 것은 include guard optimization 입니다.지금까지 설명드린 include guard 는 헤더 파일 안에 관련 매크로가 정의되기 때문에 internal include guard 라고 합니다. 그럼 internal 이 있다면 external 이 있어야 겠지요 ? 다음과 같이 하는 걸 external include guard 라고 합니다.

#ifndef HDR_H
#include "hdr1.h"
#endif

말 그대로 헤더 파일 외부에서 guard 매크로를 테스트하는 것이죠. 이렇게 하면 internal include guard 의 문제점인 컴파일 속도 문제도 해결할 수 있을 것입니다. 이런식으로 internal include guard 와 external include guard 를 같이 사용하면 중복 포함 방지와 컴파일 속도 개선 두 마리 토끼를 다 잡을 수 있게 됩니다.

이식성이 개발 과제의 중요한 목표일 경우에는 이식성이 떨어지는 #pragma once 를 사용하기 보다는 internal include guard 와 external include guard 를 활용해서 헤더 파일이 중복 포함되는 걸 방지하는 것이 더 바람직한 방법으로 생각되네요.

참고문헌
Wikipedia #pragma once
Wikipedia include guards
include guard 와 #pragma once 의 차이점
include guard optimization

이 글은 스프링노트에서 작성되었습니다.

제 글이 유익하셨다면 오른쪽 버튼을 눌러 제 블로그를 구독하세요. ->
블로그를 구독하는 방법을 잘 모르시는 분은 2. RSS 활용을 클릭하세요.
RSS에 대해 잘 모르시는 분은 1. RSS란 무엇인가를 클릭하세요.