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 언어와의 호환성을 제공한다.
'Fundamental of CS > : : C++' 카테고리의 다른 글
| [Compile Process] Static & Dynamic Library (정적, 동적 라이브러리) (0) | 2024.07.16 |
|---|---|
| [Compile Process] Assembly, Debug (0) | 2024.07.16 |
| [Compile Process] Compile Process, Header File (0) | 2024.07.16 |
| [Memory Structure] 변수 타입 (0) | 2024.06.29 |
| [Memory Structure] Variable in Memory (1) | 2024.06.28 |