.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.h
와 my_math.h
를 include 하는 my_diff.h
를 모두 include 함으로 인해 main.cpp
에는 my_sub
라는 identifier가 중복으로 사용되어 컴파일에 실패한다. 이 상황에서는 main.cpp
에서 my_diff.h
만을 include 하는 것이 간단한 해결책이 될 수 있다. 하지만 이는 my_math.h
와 my_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 |