뚜둔뚜둔✩

판다의 개발일지

C · C++

[C(++)] Header Guard (헤더 가드) Coding

2022. 2. 5. 04:18

    .h 헤더 파일은 다른 .c.cpp 파일에 의해 include 되는 파일로, 여러 파일에서 공통적으로 사용되는 함수 정의 및 매크로, 구조체 등이 정의된 파일이다.

     

    #include

    헤더 파일에는 1) 개발자가 직접 작성한 .h 헤더 파일과 2) 컴파일러가 제공하는 헤더 파일, 이렇게 2가지 종류가 있다.

    종류에 따라 #include 전처리기를 선언하는 방법이 다음과 같이 달라진다.

    1) 직접 작성 헤더 파일 #include "header_file"
    2) 시스템 헤더 파일 #include <header_file>

    그렇다면 이 #include 전처리기는 어떤 동작을 하는지 살펴보자.

     

    특정 파일에 #include 전처리기로 헤더 파일을 include 하는 것은 해당 파일에 헤더 파일 내용을 그대로 붙여 넣기 하는 것과 동일하다. 즉, #include 전처리기가 선언되면 컴파일러로의 input stream에 헤더 파일의 내용에 이어 .c 또는 .cpp 파일의 내용이 전달된다.

     

    my_math.h

    int my_sub(int bigger, int smaller)
    {
    	return (bigger - smaller);
    }

    my_diff.h

    #include "my_math.h"
    
    int abs_diff(int a, int b)
    {
    	return (a > b) ? my_sub(a, b) : my_sub(b, a);
    }

    main.cpp

    #include "my_math.h"
    #include "my_diff.h"
    
    int main(int argc, char* argv[])
    {
    	int x0 = *argv[0] - '0';
        int x1 = *argv[1] - '0';
        
        return abs_diff(x0, x1);
    }

    예를 들어, 위 코드는 0-9 사이의 좌표 값 2개를 매개변수로 입력받아 좌표 간 거리를 구하는 프로그램이다. main.cpp의 코드만 본다면 문제가 없어 보이지만 실제로 컴파일을 시도하면 다음과 같은 에러 메세지와 함께 실패하는 것을 확인할 수 있다.

    In file included from main.cpp:2:
    In file included from ./my_diff.h:1:
    ./my_math.h:1:5: error: redefinition of 'my_sub'
    int my_sub(int bigger, int smaller)
        ^
    ./my_math.h:1:5: note: previous definition is here
    int my_sub(int bigger, int smaller)
        ^
    1 error generated.

    이는 결국 main.cpp가 다음과 같이 변환되어 처리되었기 때문이다.

    // from #include "my_math.h" in main.cpp
    int my_sub(int bigger, int smaller)
    {
    	return (bigger - smaller);
    }
    
    // from #include "my_math.h" in my_diff.h
    int my_sub(int bigger, int smaller)
    {
    	return (bigger - smaller);
    }
    
    int abs_diff(int a, int b)
    {
    	return (a > b) ? my_sub(a, b) : my_sub(b, a);
    }
    
    int main(int argc, char* argv[])
    {
    	int x0 = *argv[0] - '0';
        int x1 = *argv[1] - '0';
        
        return abs_diff(x0, x1);
    }

    my_math.hmy_math.h를 include 하는 my_diff.h를 모두 include 함으로 인해 main.cpp에는 my_sub라는 identifier가 중복으로 사용되어 컴파일에 실패한다. 이 상황에서는 main.cpp에서 my_diff.h만을 include 하는 것이 간단한 해결책이 될 수 있다. 하지만 이는 my_math.hmy_diff.h, main.cpp 모두가 매우 단순하기에 가능하다는 것을 잊지 말자. 프로그램의 규모가 커지고 다양한 library를 포함하게 되면 이러한 단순한 해결책은 거의 불가능해진다.

     

    결국 이 문제에 대한 근본적인 해결책은 동일 헤더 파일이 여러 번 중복되어 include 되더라도 실질적으로는 한 번만 include 되게 하는 것이고, 여기에는 2가지 방법이 있다.

     

    1) Header Guard (헤더 가드)

    헤더 가드는 #define, #ifndef 지시문을 다음과 같은 형태로 헤더 파일에 삽입한다.

    #ifndef _MY_HEADER_H
    #define _MY_HEADER_H
    
    // declarations & definitions here..
    
    #endif
    #define identifier identifier 식별자를 정의한다.
    #ifndef identifier identifier 식별자가 정의되지 않았을 때 #if 1와 동일하다.

    헤더 가드가 적용되면 동일 헤더 파일이 처음 include 되었을 때에는 _MY_HEADER_H 식별자가 정의되지 않은 상태이기 때문에 헤더 파일이 정상적으로 include 된다. 이후 헤더 파일이 또 include 되면은 앞선(최초의) include로 인해 _MY_HEADER_H 식별자가 이미 정의된 상태이기 때문에 무시되면서 중복 identifier 사용의 문제가 해결된다.

     

    이제 헤더 가드를 앞서 보았던 예시에 적용해보자.

     

    my_math.h

    #ifndef _MY_MATH_H
    #define _MY_MATH_H
    
    int my_sub(int bigger, int smaller)
    {
    	return (bigger - smaller);
    }
    
    #endif // _MY_MATH_H

    main.cpp

    #ifndef _MY_MATH_H		// #if 1
    #define _MY_MATH_H
    
    int my_sub(int bigger, int smaller)
    {
    	return (bigger - smaller);
    }
    
    #endif // _MY_MATH_H
    
    #ifndef _MY_DIFF_H		// #if 1
    #define _MY_DIFF_H
    
    #ifndef _MY_MATH_H		// #if 0
    #define _MY_MATH_H
    
    int my_sub(int bigger, int smaller)
    {
    	return (bigger - smaller);
    }
    
    #endif // _MY_MATH_H
    
    int abs_diff(int a, int b)
    {
    	return (a > b) ? my_sub(a, b) : my_sub(b, a);
    }
    
    #endif // _MY_DIFF_H
    
    int main(int argc, char* argv[])
    {
    	int x0 = *argv[0] - '0';
        int x1 = *argv[1] - '0';
        
        return abs_diff(x0, x1);
    }

    헤더 가드의 적용으로 2번째 my_sub 함수는 #if 0으로 무시되기 때문에 (line:14-22) identifer의 중복 사용 문제없이 이젠 정상적으로 컴파일된다.

     

    2) #pragma once

    헤더 가드 대신 pragma 지시문을 사용하여 헤더 파일을 한 번만 포함하게 할 수 있다.

    #pragma once
    
    // declarations & definitions here..

    문제는, #pragma once를 지원하는 IDE 및 컴파일러가 제한적이기 때문에 일반적으로는 헤더 가드를 사용하는 것을 권장한다.

     

     

     

    References

    https://www.learncpp.com/cpp-tutorial/header-guards/

    https://docs.microsoft.com/en-us/cpp/preprocessor/preprocessor-directives

    https://docs.microsoft.com/en-us/cpp/preprocessor/once

     

    'C · C++' 카테고리의 다른 글

    [C++] 초기화 (Initialization)  (0) 2022.05.25
    [C++] 구조체(struct)와 클래스(class)  (2) 2022.05.15
    [C] printf 함수  (0) 2021.12.21
    [C] const type qualifier  (0) 2021.12.21
    [C++] namespace 사용 및 특징  (0) 2021.12.18