Fundamental of CS/: : C++

[Compile Process] 전처리기(PreProcess), 그리고 링커(Linker)의 Static & Extern

Jay.P Morgan 2024. 7. 16. 10:52

 

 

 

 

  1. PreProcess (전처리기)

 

C++ 빌드 프로세스 중 전처리기(Preprocessor)의 역할 헤더 파일(.h)과 소스 파일(.cpp)을 분리하는 이유

 

  1.1  전처리기 (Preprocessor)

 

전처리기는 컴파일 전에 소스 코드를 미리 수정하는 단계입니다. #으로 시작하는 지시문을 처리합니다.

주요 기능은 다음과 같습니다.

 

① 조건부 컴파일

  • #if, #ifdef, #ifndef, #else, #endif
  • 특정 조건에 따라 코드를 포함하거나 제거합니다.
  • 컴파일러는 전처리된 결과만 보게 됩니다.

예시

#ifdef DEBUG
    cout << "Debug";
#endif

→ DEBUG가 정의되어 있을 때만 해당 코드가 컴파일됩니다.

 

 

② 매크로 치환 (#define)

#define MAX 100

전처리기가 MAX를 100으로 바꿉니다.

하지만 모던 C++에서는 매크로 사용을 최소화하는 것이 권장됩니다.

대신

  • constexpr
  • const
  • std::numeric_limits
  • std::max()

등의 표준 기능을 사용하는 것이 더 안전합니다.

 

 

③ 미리 정의된 매크로

대표적으로

  • __FILE__
  • __LINE__
  • __DATE__
  • __TIME__

등이 있으며,

  • 현재 파일 이름
  • 현재 줄 번호
  • 컴파일 날짜
  • 컴파일 시간

등을 얻을 수 있습니다.

 

 

  2.  #include의 역할

 

#include "Cat.h"

전처리기는 헤더 파일 내용을 그대로 복사해서 붙여넣습니다.

main.cpp
    ↓
#include "Cat.h"

Cat.h 내용이 main.cpp 안으로 복사

이후 하나의 Translation Unit이 만들어지고 컴파일됩니다.

 

include 사용 규칙

  • 표준 라이브러리
#include <vector>
  • 사용자가 만든 헤더
#include "Cat.h"

 

어디에 include 해야 하나?

가능하면

  • CPP 파일에 include

헤더에서 실제 타입이 필요한 경우만

  • 헤더에서 include

하는 것이 좋습니다.

 

 

  3.  헤더가 여러번 포함되는 문제

 

예를 들어

main.cpp
    ↓
Cat.h

Zoo.h
    ↓
Cat.h

처럼 같은 헤더가 두 번 포함될 수 있습니다.

그러면

  • 클래스 중복 선언
  • 컴파일 오류

가 발생합니다.

이를 막기 위해

#pragma once

또는

#ifndef CAT_H
#define CAT_H

...

#endif

(Include Guard)를 사용합니다.

현재는 #pragma once 사용을 권장합니다.

 

 

  4.  헤더(.h)와 CPP(.cpp)를 나누는 이유 

 

예를 들어

void foo();

 선언(Declaration)

void foo()
{
    ...
}

 정의(Definition) 입니다.

 

 

프로젝트에서는

Foo.h

void foo();

Foo.cpp

void foo()
{
    ...
}

처럼 분리합니다.

 

 

  5.  컴파일과 링크 과정

 

예를 들어

main.cpp

에서

foo();

를 호출하면

컴파일러는

"foo 함수가 존재한다."

는 선언만 있으면 컴파일을 진행합니다.

하지만 실제 구현이 없으면 링크(Link) 단계에서

Undefined reference

오류가 발생합니다.

  • 컴파일 → 선언만 필요
  • 링크 → 실제 정의 필요

입니다.

 

 

  6.  여러 CPP 파일의 빌드

 

프로젝트 예시

main.cpp
Cat.cpp
Dog.cpp

Cat.h
Dog.h

각 CPP 파일은 독립적으로 컴파일되어

main.o
Cat.o
Dog.o

가 만들어지고,

링커가 이를 합쳐 하나의 실행 파일을 생성합니다.

헤더 파일은 여러 CPP 파일이 동일한 선언을 공유하기 위해 사용됩니다.

 

 

  ※ 핵심 정리

 

  • 전처리기는 #include, #define, #ifdef 등을 처리하여 컴파일 전에 코드를 수정한다.
  • #include는 헤더 파일을 복사해서 붙여넣는 동작이다.
  • 매크로(#define)보다 constexpr, const, 표준 라이브러리 사용이 권장된다.
  • 헤더의 중복 포함은 #pragma once 또는 Include Guard로 방지한다.
  • 헤더 파일(.h)에는 선언(Declaration), CPP 파일(.cpp)에는 구현(Definition)을 작성하는 것이 일반적인 C++ 구조이다.
  • 각 CPP 파일은 독립적으로 컴파일되어 오브젝트 파일(.o)이 되고, 마지막에 링커가 이를 합쳐 실행 파일을 만든다.

 

 

  2.  Linker의 Static & Extern

 

C++ 빌드 프로세스에서 extern과 static 키워드의 역할 링커(Linker)에서 이들이 어떻게 동작하는가

 

  2.1  extern과 static의 의미

 

두 키워드는 링크(Linkage) 범위를 결정합니다.

  • extern : "이 변수(또는 함수)의 정의는 다른 파일에 있다."
     외부 링크(External Linkage)
  • static : "이 변수(또는 함수)는 현재 파일에서만 사용한다."
     내부 링크(Internal Linkage)

※ 여기서의 static은 빌드/링크와 관련된 의미이며, 클래스의 static 멤버나 정적 저장 기간(static storage duration)과는 다른 개념입니다.

 

 

  2.  변수에서의 extern

 

예를 들어 두 개의 CPP 파일에서

int a = 0;
int a = 100;

처럼 같은 전역 변수를 정의하면

  • 컴파일은 성공하지만
  • 링크 단계에서 "Multiple Definition" 오류가 발생합니다.

이를 해결하려면

extern int a;

처럼 선언만 하고,

다른 CPP 파일에서만

int a = 100;

처럼 실제 정의를 합니다.

그러면 링커가 정의된 변수를 찾아 연결합니다.

 

 

  3.  변수에서의 static

 

static int a = 100;

이라고 선언하면

이 변수는

  • 현재 CPP 파일에서만 사용 가능
  • 다른 파일에서는 접근 불가

가 됩니다.

즉, 같은 이름의 전역 변수가 다른 CPP 파일에 있어도 서로 충돌하지 않습니다.

 

  4.  함수에서의 extern

 

함수는 기본적으로 외부 링크(External Linkage) 를 가집니다.

즉,

void foo();

는 사실상

extern void foo();

와 같은 의미입니다.

따라서 다른 CPP 파일에서 정의된 함수를 링커가 자동으로 찾아 연결합니다.

 

  5.  함수에서의 static

 

static void helper()
{
}

처럼 선언하면

이 함수는

  • 현재 CPP 파일에서만 호출 가능
  • 다른 CPP 파일에서는 호출 불가

입니다.

따라서 내부 구현용(helper) 함수는 static으로 만드는 것이 안전합니다.

 

  6.  클래스에서의 활용

 

예를 들어

Cat::Speak()

안에서만 사용하는 보조 함수가 있다면

멤버 함수로 만들기보다

static void Helper()

처럼 파일 내부에서만 사용하는 자유 함수(free function) 로 만드는 것이 더 적절한 경우가 있습니다.

이는

  • 클래스 인터페이스를 단순하게 만들고
  • 외부 노출을 줄이며
  • 의존성을 감소시킵니다.

  7.  extern "C"

 

C++에서는 함수 이름을 그대로 저장하지 않습니다.

컴파일 과정에서

foo(int)
foo(double)

처럼 오버로딩을 지원하기 위해

_Z3fooi
_Z3food

같은 형태로 이름을 변경하는데,

이를 Name Mangling(이름 맹글링) 이라고 합니다.

 

하지만 C 언어는

  • 함수 오버로딩이 없고
  • Name Mangling도 하지 않습니다.

그래서 C 라이브러리와 연동하려면

extern "C"
{
    void foo();
}

를 사용합니다.

이렇게 하면

  • 이름이 변경되지 않고
  • C 방식 심볼 이름이 생성되어
  • C 라이브러리와 호환됩니다.

 

  ※  핵심 정리

 

  • extern : 다른 파일에 정의된 변수나 함수를 사용하겠다는 의미(외부 링크).
  • static : 현재 CPP 파일 내부에서만 사용할 수 있도록 제한(내부 링크).
  • 전역 변수를 여러 파일에서 정의하면 링크 오류(Multiple Definition) 가 발생하므로, 한 곳에서만 정의하고 다른 곳에서는 extern으로 선언한다.
  • 함수는 기본적으로 extern이며, 파일 내부에서만 사용하는 보조 함수는 static으로 선언하는 것이 좋다.
  • extern "C" 는 C++의 Name Mangling을 비활성화하여 C 언어와의 호환성을 제공한다.