전처리기와 헤더가드

전처리기

 

# 기호로 시작하고 세미콜론 없이 끝나는 명령문이다. 이러한 지시문은 전처리기에 특정 텍스트 조작 작업을 수행하도록 지시한다.

예를 들어 파일을 #include 하면, 전처리기는 #include 지시문을 include에 있는 파일의 내용으로 바꾼다.

그런 다음 include에 있던 내용들이 다시 전처리되고( #include가 재귀적으로 전처리될 수 있음) 파일의 나머지 부분이 전처리된다.

 

 

#include 외에 전처리기 중 하나인 매크로는 #define 지시문을 사용해서 만들 수 있다. 

 

매크로에는 객체형 매크로  함수형 매크로라는 두 가지 기본 유형이 있다 .

함수형 매크로는 함수처럼 작동하며 목적 또한 비슷하다.

객체형 매크로는

#define identifier
#define identifier substitution_text

둘 중 하나로 정의할 수 있다. (대체텍스트 유무)

 

전처리기가 대체텍스트가 있는 객체형 매크로를 발견하면, 식별자는 대체텍스트로 표현된다.

 

그럼 대체텍스트가 없으면 식별자는 그냥 공백으로 바뀌는 것일까?

>> 매크로는 전처리기가 아닌 명령에 대해서만 텍스트 대체를 발생시키므로, #define identifier는 텍스트 대체 적용이 안된다.

 

 

 

 

 

조건부 컴파일  #ifdef , #ifndef , #endif 

#include <iostream>

#define PRINT_JOE

int main()
{
#ifdef PRINT_JOE
    std::cout << "Joe\n"; // will be compiled since PRINT_JOE is defined
#endif

#ifdef PRINT_BOB
    std::cout << "Bob\n"; // will be excluded since PRINT_BOB is not defined
#endif

    return 0;
}
 

이 예시에서 PRINT_JOE는 #define되었기 때문에 해당 행 std::cout << "Joe\n"이 컴파일된다. PRINT_BOB은 #define되지 않았기 때문에 std::cout << "Bob\n"은 무시된다.

 

여기에서 알 수 있는 사실로 #ifdef는 정의됐으면 해당 내용 출력하고 어디까지 출력할 것인지를 #endif로 정한다.

 

 

#ifndef는 #ifdef 와 반대로 정의되지 않으면 실행한다.

 

추가로, #ifdef PRINT_BOB 대신
#if defined(PRINT_BOB) 또는   #if !defined(PRINT_BOB)으로 표현할 수도 있다.

 

 

#if 0 // Don't compile anything starting here
    std::cout << "Bob\n";
    std::cout << "Steve\n";
#endif // until this point

 

위와 같이 #if 0 을 사용하여 주석처리를 할 수 있다. /*   */처럼 여러 줄 코드를 주석처리할 때 사용된다. 활성화 하려면 #if 1로 바꿔준다.

 

전처리기가 완료되면 해당 파일에 정의된 모든 식별자가 삭제된다. 이는 지시어가 정의 시점부터 정의된 파일의 끝까지만 유효함을 의미한다.

그러므로 하나의 코드 파일에 정의된 지시문은 동일한 프로젝트의 다른 코드 파일에 영향을 미치지 않는다.

 

 

 

 

헤더파일

 

프로그램이 커지고 더 많은 파일을 사용하게 되면 사용하려는 모든 함수들을 전부 하나의 파일에 선언해야될까?

우리는 차라리 모든 전방 선언을 한 곳에 모아두고 필요할 때 가져오고 싶다.

 

여기에서 다른 타입들이 있는 파일을 헤더 파일 이라고 한다.

헤더 파일의 주요 목적은 선언을 여러 코드(.cpp) 파일에 전파하는 것이다.

 

헤더 파일을 사용하면 선언을 헤더 파일에 넣은 다음 필요할 때마다 가져올 수 있다. 이렇게 하면 다중 파일 프로그램에서 입력하는 시간을 많이 줄일 수 있다.

 

 

흔히 우리가 아는 예로 #include <iostream> 전처리기를 이용해 <iostream>이라는 파일의 모든 콘텐츠를 #include가 있는 파일로 복사하도록 요청할 수 있다.

만약 여러개의 파일에 <iostream>을 갖고 오고 싶다면, 일일이 전방선언할 필요 없이 #include 헤더파일을 통해 간편하게 전부 가져올 수 있다. (헤더와 코드파일이 쌍을 이루는 경우 둘 다 동일한 이름을 가져야 한다.)

 

 

main.cpp에서 이 헤더 파일 .h를 사용하려면 #include해야 한다(꺾쇠 괄호가 아닌 따옴표 사용).

꺾쇠 괄호를 사용하면, 이건 우리가 직접 작성하지 않은 헤더 파일이라고 전처리기에 알려주는 것이고,

큰따옴표를 사용하면, 이건 우리가 작성한 헤더 파일이라고 전처리기에 알려준다는 의미이다.

 

 

 

 

헤더파일 예시

 add.h:

// 1) We really should have a header guard here, but will omit it for simplicity (we'll cover header guards in the next lesson)

// 2) This is the content of the .h file, which is where the declarations go
int add(int x, int y); // function prototype for add.h -- don't forget the semicolon!
 

main.cpp에서 이 헤더 파일을 사용하려면 #include해야 한다

(<iostream>같이 꺾쇠 괄호가 아닌 따옴표 사용).

 

 

main.cpp:

#include "add.h" // Insert contents of add.h at this point.  Note use of double quotes here.
#include <iostream>

int main()
{
    std::cout << "The sum of 3 and 4 is " << add(3, 4) << '\n';
    return 0;
}
 

 

add.cpp:

#include "add.h" // Insert contents of add.h at this point.  Note use of double quotes here.

int add(int x, int y)
{
    return x + y;
}

 

 

 

 

 

 

헤더 파일의 잘못된 사용 방법

헤더 파일에 함수나 변수 정의를 넣는 것은 피해야 된다. 

그렇게 하면 헤더 파일이 둘 이상의 소스 파일에 포함되는 경우, 일반적으로 단일 정의 규칙을 위반하여 오류를 발생한다.

 

add.h:

definition for add() in header file. don't do this!

int add(int x, int y)
{
    return x + y;
}
 

 

main.cpp:

#include "add.h" // Contents of add.h copied here
#include <iostream>

int main()
{
    std::cout << "The sum of 3 and 4 is " << add(3, 4) << '\n';

    return 0;
}
 

 

add.cpp:

#include "add.h" // Contents of add.h copied here

 

여기서 컴파일러가 add.cpp 을 컴파일할 때 

#include "add.h"는  add.h의 내용 그대로 대체된 다음 컴파일된다. 대체되면 선언이 두번 되는것이니 당연히 오류이다.

즉, 헤더파일은 전부 대체되니 전방선언만 넣고, 다른 파일들에 선언을 하자.
 
 
 

 

 

 

헤더가드 (include 가드)

 

만약 헤더 파일이 다른 헤더 파일을 #include하게 되어 헤더 파일의 정의가 두 번 이상 포함된다면 좀 당황스러울 것 같다.

이런 경우 헤더가드 라는 메커니즘을 통해 위의 문제를 해결할 수 있다.

 

헤더 가드는 다음 형식을 취한다. 안어렵다. 이미 다 배운걸 활용하는 것이다.

#ifndef SOME
#define SOME

// your declarations (and certain types of definitions) here

#endif

이 헤더가 #include되면 전처리기는 SOME 이 이전에 정의되었는지 확인한다.

처음이라 아직 없다? 그럼 다음 지시문인 SOME 을 #define 하고 파일 내용을 포함한다. 

 

헤더가 동일한 파일에 다시 포함되면 SOME 은 헤더의 내용이 처음 포함될 때 이미 정의되어 있으며 헤더의 내용은 무시된다(#ifndef 덕분에). 이렇게 헤더 가드는 중복 포함을 방지한다.