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

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




(podics.com 에 올려 놓은 이 글에 대한 podcast도 같이 연결해 놓았습니다. 플레이 버튼을 누르고 아래 글을 읽으시면 이해에 도움이 될 것입니다)

기존에 제가 써 오던 C++ 이야기 시리즈와는 별도로 얘기하고 싶은 것이 있어서 새로운 시리즈로 "고수들이 절대 가르쳐 주지 않는 C/C++ 프로그래밍 팁"을 시작해 볼까 합니다. 시리즈 이름이 너무 길어서 고절가주팁이라고 줄여서 말해렵니. ^^ 얼마나 많이 나올 수 있을지는 모르겠지만 어찌 됐든 또 쭉~ 한 번 가보는 거죠.

첫번째 팁으로는 헤더 파일이 중복 포함되는 걸 방지하는 법에 대해 설명드릴까 합니다. 다 알고 계신다구요 ? 예~ 그러시겠죠. 알고 계신다면 C/C++ 프로그래밍 고수임에 틀림이 없습니다. 그러시다면 아래까지 쭉~ 읽어 보시고 잘못된 부분은 없나 추가할만한 얘기는 없나 보고 댓글 남겨 주시는 센~스! 부탁드리겠습니다.

제가 첫 회사에 입사하고 나서 처음으로 프로젝트를 할 때였습니다. 그 때, 누가 요구 분석하고, 구조 설계하고, 상세 설계하고 가이드해 줄 선배가 없었습니다. 제가 처음으로 해 본 프로젝트이자 회사도 처음으로 해 본 프로젝트였으니까요(저의 첫 회사는 조그만 중소기업이었습니다). 거의 그냥 맨땅에 헤딩하다시피 하면서 열정과 패기만으로 프로젝트를 수행하고 있었습니다.

정말 한참 동안 코딩을 하고 나서(머리 속으로 설계하고 바로 코딩에 들어갔죠. 좋은 방법은 아닙니다. 오해 없으시길...), 처음으로 컴파일을 시도했습니다. 무식하면 용감하다고 제 기억으로 거의 이삼만 라인 이상을 작성하는 동안 한 번도 컴파일하지 않다가 처음으로 그것도 보무도 당당하게 컴파일을 시도했습니다. 이제 컴파일만하고 실행시켜 보면 된다. 제 머리속에서는 완벽하게 돌았으니깐 문제 없을거야하며 엔터를 치는 순간 아니! 이게 웬 일입니까. 정말 수 많은 에러들이 수십 페이지가 넘도록 발생하다가 결국 컴파일러가 에러가 너무 많아서 컴파일할 수 없다고 포기해 버리는 것 아니겠습니까 ?

그 수 많은 에러를 보는 순간 어찌나 눈 앞이 캄캄하던지... 개발해 보면서 컴파일 에러에 압도된 적이 있으신가요 ? 우와~ 정말 미치겠더군요. 저는 그 때 정말 에러 메시지에 압도 돼 버렸습니다. 순간 개발자라는 직업이 나를 거부하나 보다 지금이라도 직종 전환할까하고 생각도 했습니다. 한동안 정신 못 차리고 있다가 겨우 마음을 추스리고 나서 이틀 내내 그 많은 컴파일 에러들 잡느라 고생 고생했습니다. 거짓말 아닙니다. 정말 컴파일 에러만 없애는데 이틀이 꼬박 걸렸습니다.

그런데 그 중에 제일 많은 에러를 발생시킨 게 어떤 거였는지 상상이 되십니까 ? 그건 바로 오늘의 주제인 헤더 파일 중복 포함 때문이었습니다. 그때 저는 프로그래밍 경험이 일천한지라, 그리고 누가 가르쳐 주는 사람도 없는지라 헤더 파일이 중복 포함되는 걸 방지하는 방법을 쓰는 게 아니라 헤더 파일 포함 순서를 바꿔보고, 코드를 이리 저리 옮겨 보고 하는 식으로 정말 죽을 똥 살 똥하면서 그 많은 컴파일 에러를 잡아 냈습니다. 정말 인간 승리였죠.

그땐 정말 악몽 같았지만 그런 경험이 있기 때문에 오늘 이런 글을 쓸 수 있는 것 아니겠습니까 ? 제가 이렇게 장황하게 제 개인적인 경험 이야기를 늘어 놓는 건 이 팁이 사소하게 느껴질 수도 있지만 정말 중요한 팁이라는 걸 말씀드리고 싶었기 때문입니다. 아마 보통은 제가 말씀 드리려는 이 팁이 회사의 Coding Standard 에 들어 있어서 그걸 따르면 제가 겪었던 문제는 발생하지 않을 것입니다. 어떻게 보면 그런 문제를 해결한 경험이 이미 회사의 무형 자산으로 쌓여 있는 것이겠죠. 아니면 이미 경험한 선배들의 머리 속에 있거나요.

자! 그럼 제가 경험한 문제를 여러분도 한 번 실제로 경험해 볼 수 있도록 다음 코드를 작성한 후 컴파일 해 보시겠어요 ?

// hdr1.h 의 내용
class DummyBase {
private:
  int _i;

public:
  DummyBase(int i): _i(i) {
  }

  int Get() {
    return _i;
  }

  void Set(int i) {
    _i = i;
  }
};

// hdr2.h 의 내용
#include "hdr1.h"              // hdr1.h 을 여기서 include 하고 있습니다

class Dummy: DummyBase {
public:
  Dummy(int i): DummyBase(i) {
  }

  int operator() () {
    return Get();
  }

  int operator() (int multi) {
    return Get() * multi;
  }
};

// tips1.cpp 의 내용
#include "hdr1.h"           // tips1.cpp 에서도 hdr1.h 을 include 하네요
#include "hdr2.h"

int
main()
{
  DummyBase db(200);
  Dummy d(100);
}


이미 중복 포함 문제가 어떻게 발생하는지를 아시는 분은 어떤 에러가 어떤 지점에서 발생될지 상상이 되시죠 ? 제가 사용하는 컴파일러로는 다음과 같은 에러가 발생하네요.

c:\documents and settings\김윤수\my documents\visual studio 2005\projects\tips\tips1\hdr1.h(1) : error C2011: 'DummyBase' : 'class' type redefinition
        c:\documents and settings\김윤수\my documents\visual studio 2005\projects\tips\tips1\hdr1.h(1) : see declaration of 'DummyBase'
c:\documents and settings\김윤수\my documents\visual studio 2005\projects\tips\tips1\hdr2.h(3) : error C2504: 'DummyBase' : base class undefined
c:\documents and settings\김윤수\my documents\visual studio 2005\projects\tips\tips1\hdr2.h(5) : error C2614: 'Dummy' : illegal member initialization: 'DummyBase' is not a base or member
c:\documents and settings\김윤수\my documents\visual studio 2005\projects\tips\tips1\hdr2.h(9) : error C3861: 'Get': identifier not found
c:\documents and settings\김윤수\my documents\visual studio 2005\projects\tips\tips1\hdr2.h(13) : error C3861: 'Get': identifier not found
Build log was saved at "file://c:\Documents and Settings\김윤수\My Documents\Visual Studio 2005\Projects\Tips\Tips1\Debug\BuildLog.htm"
Tips1 - 5 error(s), 0 warning(s)


어때요 ? 보기만 해도 그냥 갑갑하시죠 ? 제가 표시한 파란 부분을 보니 'DummyBase' 클래스가 재정의됐다고 하네요. 그 다음 에러들은 다 첫번째 에러 때문에 파생된 에러들입니다. 이런 에러 패턴이 헤더 파일이 중복 포함되어 발생하는 것이라는 걸 알고 있는 사람은 해결하기가 쉽지만 잘 모르는 사람은 문제를 해결하는데 한참이 걸리게 마련입니다. 에러 메시지 자체가 클래스가 재정의됐다고만 말하고 있기 때문에 헤더 파일이 중복 포함되었다는 데까지 생각이 미치기 어려고, 게다가 그 다음에 딸려 오는 에러들은 진짜 문제점(root cause)를 찾는 데 방해만 되기 때문입니다.

자~ 그럼 이 문제를 어떻게 해결할 수 있을까요 ? 제가 생각할 수 있는 방법은 다음과 같습니다.

1) hdr1.h, hdr2.h 의 내용을 모두 tips1.cpp 로 모두 옮긴다. ^^;
2) hdr1.h 의 내용을 hdr2.h 로 옮기고 tips1.cpp 에서는 hdr2.h 만 include 한다.
3) 다른 건 다 그대로 놔두고 tips1.cpp 에서 hdr1.h 은 include 하지 않는다.

이것말고 다른 방법이 떠 오르시나요 ? 위 방법들은 여기에 제시된 코드에만 적용할 수 있는 임시방편적인 방법이라고 할 수 있구요, 좀 더 근본적인 방법은 모든 헤더 파일을 다음과 같이 작성하는 겁니다.

// 헤더 파일의 제일 첫 부분
01: #ifndef HDR_H     // 헤더 파일명을 대문자한 매크로가 정의되어 있지 않으면
02: #define HDR_H     // 헤더 파일명 매크로를 정의한다

......                // 헤더 파일 본 내용이 여기 들어갑니다

03: #endif            // ifndef HDR_H 에 매치됩니다


위와 같이 헤더 파일을 작성하게 되면 다음과 같은 과정이 일어나게 됩니다. 컴파일러와 역지사지해서 생각해 보겠습니다.

1. 처음으로 헤더 파일이 포함될 때, 컴파일러(좀 더 정확히 말하면 프리프로세서)가 01 라인을 만나게 되면 HDR_H 매크로가 정의되어 있지 않으므로 02 라인을 해석하게 됩니다.
2. 02 라인을 만나면 이제 HDR_H 매크로를 정의하게 됩니다.
3. 다음에 다시 같은 헤더 파일이 포함될 때는 HDR_H이 정의되어 있기 때문에 모든 헤더 파일의 내용을 스킵하게 됩니다. 즉, 포함되지 않는 효과가 생기게 됩니다.

자~! 그럼 여기 예제를 다시 수정한 후에 컴파일 해 볼까요 ?

// hdr1.h 의 내용
#ifndef HDR1_H
#define HDR1_H


class DummyBase {
private:
  int _i;

public:
  DummyBase(int i): _i(i) {
  }

  int Get() {
    return _i;
  }

  void Set(int i) {
    _i = i;
  }
};

#endif

// hdr2.h 의 내용
#ifndef HDR2_H
#define HDR2_H


#include "hdr1.h"              // hdr1.h 을 여기서 include 하고 있습니다

class Dummy: DummyBase {
public:
  Dummy(int i): DummyBase(i) {
  }

  int operator() () {
    return Get();
  }

  int operator() (int multi) {
    return Get() * multi;
  }
};

#endif

다시 컴파일해 보시면 에러가 발생하지 않는 걸 확인하실 수 있을 겁니다. 이해가 되시나요 ? 아마 요즘 대부분의 개발 조직에서 이 정도의 팁은 Coding Standard 로 가지고 있지 않을까 합니다. Coding Standard 에 왜 그런 항목이 있었는지를 이해하실 수 있다면 더 좋겠지요. 이유는 헤더 파일이 중복 포함되어 발생하는 컴파일 에러를 방지하기 위한 목적입니다.

여러분 컴퓨터에 설치되어 있는 헤더 파일을 열어 보면 위와 같이 헤더 파일이 중복 포함되는 걸 방지하기 위한 방법을 사용하고 있는 걸 확인하실 수 있을 겁니다.

그럼, 이번 글은 이 정도로 마무리 하고, 다음에는 C/C++ Mixed Programming 기법에 대해 설명드리도록 하겠습니다. 고절가주팁이 쭉~ 갈 수 있도록 많은 관심 부탁드립니다. 여러분이 알고 있는 팁을 트랙백이나 댓글로 좀 알려 주시면 더할 나위 없이 좋겠네요. 그리고, 혹시 Podcast 로 들으신 분들은 느낌이 어떤지, 이해에 도움이 되는지 댓글로 좀 남겨 주시면 감사하겠습니다.

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