SomeHeader.h

#ifndef _SOME_HEADER_
#define _SOME_HEADER_

// 헤더 내용들이 많이 있다.

#endif


OtherHeader.h

#pragma once

// ... 뭐 있겠지



두 가지 방식의 차이점이라든지 비교우위 등을 알고 싶어하는 이들이 많다.

짤막하게 짚고 넘어가자면 전자(SomeHeader)는 언어 차원의 표준이며 컴파일러가 항상 지원해야하는 믿을 수 있는 기능이고

후자(OtherHeader)는 일부 성능 좋은(?) 컴파일러만 지원하는 변종 기능이다.

변종인 만큼 후자의 방식은 전처리기가 아예 파일도 열어보지 않고 컴파일을 건너뛰고,

전자는 일단 파일을 열어보고 건너뛴다는 차이점을 가지며, 변종이 조금 더 속도에서 우세하다고 한다.



맞는 얘기들이었지만 사실 이젠 좀 철 지난 이야기들이다.

실제로 #ifndef 든 #pragma 든 둘 다 전처리기(Preprocessor) 지시자이며 컴파일을 하기 전에 먼저 처리되는 것들이다.

#ifndef 와 #endif (#else 라든지 #elif 도 좋겠다) 사이에 수백만줄 정도가 있다면 #pragma once 와 차이가 좀 벌어질 수 있으나

일반적인 기준에서 잘 짜여진 헤더파일(그 자체로 문서로 여겨질 수 있는 요약본 형식의)의 크기 안에서는

그럭저럭 무난하게 무시해줄 수 있다.
(물론 그렇다고 해서 그 헤더 파일이 수천번 include 되어도 무난하다는 소리는 절대 아니다)


중요한 것은 정말로 파일 전체가(꼭 헤더에 한정되지 않는다) 동일 obj 에서 한 번만 로딩된다면 #pragma once 도 괜찮겠지만

다른 조건들이 포함되어 있다면 #ifndef 를 활용하는 것이다. #ifndef 의 원래 용도를 잘 생각해보자.
(혹자는 #pragma once 가 표준이 아님을 들어 #ifndef 를 권하고 있으나 사실 가장 널리 사용되는 상위 컴파일러들은 모두 지원하고 있으므로 딱히 문제가 되진 않는다)

만약 헤더 파일이 수천줄 쯤 된다면, 그리고 그 헤더 파일을 많은 곳에서 include 한다면 #ifndef 보다는 #pragma once 를

쓰는 것이 확실히 전처리 속도에 도움이 된다.


감이 잘 안 오는 사람들을 위해 아래와 같은 스샷을 마련했다


 

대충 파일 구조는 위의 것을 보면 쉽게 알 수 있을 것이고

컴파일 결과를 그냥 IDE에서 컴파일해버리면 알기 어려우니 직접 명령어를 조작하여 컴파일을 시도해보는 것이 좋다.

원래 영어 IDE 를 사용하지만 스샷을 위해 한글을 마련했다. 왕친절한 것 같다.


 

 
<아 이렇게 친절할 수가.jpg> 



result.cpp 의 내용을 보자


 

드문드문 보이는 빈 공백들은 바로 전처리기가 #ifndef ~ #endif 같은 조건에 걸려 삭제해버린 줄들이다.
(물론 쓸모 없는 전처리기 구문 자체도 지우기 때문에 원래 3번 라인 밑에 나와야 하는 #ifndef _IFNDEF_ 같은 구문이 모두 빈줄로 대체되어 있다)


other.cpp 의 경우를 보아야 #ifndef 와 #pragma once 의 차이점을 확실히 알 수 있다.

other.cpp 는 22줄에서 보다시피 처음으로 ifndef.h 파일을 포함하였는데 이후 middle.h 에서 다시 한 번 포함한다(36줄)

그러나 앞서 이미 포함되어 있었으므로 모두 공백으로 대체되었다.

그러나 30줄에서 보다시피 other.cpp 는 pragmaonce.h 파일도 먼저 포함하였는데 이후 44번째 줄에서 보다시피

middle.h 가 선언한 #include "pragmaonce.h" 명령은 공백으로 대체되어 아예 포함조차 시도하지 않았다.

이것을 통해 #ifndef 와 #pragma once 의 실제 움직임을 조금은 따라가 볼 수 있는가?



#line 지시문에 대해서는 관심있는 사람들에 한해 찾아보도록 남겨두겠다.



그런데 주의할 점이 있다. 여기서 1회 포함 및 생성이란 얘기는 "파일 단위"에서의 이야기다.

그리고 이것이야말로 Unity Build 라는 기법이 왜 필요한가의 이유가 된다.


내가 심심해서 main.cpp 와 other.cpp 두 개를 만들지는 않았을 것이다.

우리는 보통 #ifndef (또는 #pragma once) 를 사용하면 헤더 파일을 전체 프로젝트에서 한 번만 include 할 것이라고

착각할 수 있는데(또는 별 신경 안 쓰고 넘어갈 수 있는데) 위에서 보다시피, 사실이 아니다.

한 번만 include 하는 것은 모두 오브젝트 단위에서의 이야기다.

오브젝트가 다르면 제 아무리 이전에 8만줄짜리 파일을 포함하여 컴파일을 이미 진행했다 하더라도

컴파일러는 다시 컴파일을 진행해버린다는 이야기다.

이런 짓을 하지 않기 위해 Unity Build 라는 스킬이 등장한 것이고 이와 관련해서는 서툰 내 글솜씨보다

훨씬 잘 된 자료가 있으므로 그것을 공유하며 이 포스팅을 마칠까 한다


http://www.slideshare.net/devcatpublications/ndc2010-unity-build




 


Posted by OOJJRS

댓글을 달아 주세요

  1. arsmap 2014.01.20 18:36 신고  댓글주소  수정/삭제  댓글쓰기

    안녕하세요, #if 관련 자료를 찾다가 들리게 됐습니다.
    자세한 설명 감사드립니다. 위 적어 주신 설명들을 읽다가 한가지 궁금한 점이 있어 여쭤봅니다.

    SomeHeader.h

    #ifndef _SOME_HEADER_
    #define _SOME_HEADER_

    이와 같이 쓰셨는데요, 사실 대부분의 프로그래머들 소스를 보면 위와 같이 되어 있더군요.

    SomeHeader.h

    #ifndef SOMEHEADER.H
    #define SOMEHEADER.H

    하지만, 전 이렇게 계속 사용을 해왔답니다. 위 적어주신 방법과 제가 현재 쓰고 있는 방법 사이에
    어떤 차이점이 있는지요.
    언더바 가 어떤 특정한 문자를 지칭하는 건지도 궁금하네요.

    • OOJJRS 2014.02.02 22:37 신고  댓글주소  수정/삭제

      안녕하세요 :-)

      두 개는 표현의 차이 외에는 전혀 다른 것이 없습니다.

      #ifndef, #define 뒤에 나타나는 문자열은 '고유하기만 하면 문제 없음' 이기 때문에

      언더바를 쓰거나 방문자 님께서 쓰신 것처럼 하거나 전혀 상관 없습니다.

      언더바가 무엇을 지칭하는지에 대해서는 사용하는 사람마다 좀 다릅니다.
      (보통은 공백을 의미하거나, 단어 단락별 구분을 의미하거나 로 많이 사용합니다)


      관심이 있으실 것 같아 과거 이야기를 해보자면 과거에는 콘솔 상에서 빌드를 했기 때문에

      지금의 VS처럼 자동으로 컴파일 명령을 만들어주고 실행해주는 것이 아니라 작업자가 일일이 명령어 옵션을 설정하고 실행했는데요.

      그 중 가장 대표적인 옵션 중 하나가 플래그의 define 옵션이었습니다 (gcc에서는 -D 를 씁니다. VS에서의 CL.exe 는 /D)

      그럼 다음과 같은 모양새가 되는데요

      cl.exe /D FLAGNAME Example.cpp
      gcc -DFLAGNAME Example.cpp

      이때 gcc의 경우 FLAGNAME 이 -D 와 딱 붙어버리면 모양새가 별로여서 맨 앞에 언더바를 붙여 구분하기 시작했습니다.

      cl.exe /D _FLAGNAME Example.cpp
      gcc -D_FLAGNAME Example.cpp


      아 저런 내막(?)이 있었구나...라는 위의 느낌을 살려 다음을 보시면.

      헤더 파일에 사용하는 플래그 이름은 고유해야 하며 규칙성이 복잡하지 않아야 만들기 편하므로
      (자동화시킬 수 있으므로)

      파일명을 많이 사용했는데요.

      당시에는 지금처럼 폴더나 파일이 많지 않을 뿐더러 한 폴더에 몰려 있는 경우도 많아서 파일명 자체가

      고유한 경우가 꽤 있었습니다. 따라서 위에서 보시는 것처럼

      #ifndef _파일명_H_

      와 같은 방식을 많이 쓰게 되었습니다.
      (자동화를 위해 복잡하지 않으면서도 보기 좋고 고유성을 보장하는 규칙을 찾다보니... 랄까요)

      #define 은 따로 컴파일 시에 -D 옵션을 쓸 필요는 없으나 관습적 사용이라고 보시면 되겠습니다.
      (플래그명 통일을 위한)



      답변이 늦어 죄송합니다. 제 블로그는 방문자들의 질문이 잘 달리지 않아서 확인이 늦었습니다. :-)

      감사합니다.

      ps : 설명이 혼선되어 있어 몇 번 수정했습니다.