C++로 작성된 프로그램을 빌드하는 과정은 4단계로 이루어진다.
- 전처리 단계
- 전처리기가 처리
- 작성된 코드의 문법적 검토
- 주석 제거
- 실제 컴파일 가능한 소스로 변환
- 컴파일 단계 - 파일 단위로 처리 (cpp)
- int number = 10;
- 컴파일러가 처리
- 소스 코드를 어셈블러 코드로 변환
- 어셈블리 단계
- 어셈블러가 처리
- 중간 파일인 obj 파일로 변환
- 링크 단계
- 링커가 처리
- obj 파일을 연결해 실행 가능한 파일 생성

전처리 단계
코드를 컴파일하기 전에 처리하는 단계이다.
#이 붙은 구문은 모두 전처리에 해당한다고 보면 된다. (ex. #include, #pragma, #define 등)
전처리는 전처리기가 처리하며, 현재 작성된 코드의 문법적 오류가 없는지 검사한다.
또한, 실제 컴파일에서 사용되지 않는 주석들을 모두 제거한다.
그리고 실제 컴파일 가능한 소스로 변환하는데, 라이브러리를 포함하는 #include <iostream> 같은 구문을 예로 들면, iostream 라이브러리로 가서 해당 코드들을 모두 복사해서 #include <iostream> 구문을 제거하고 붙여 넣는 것이다.
그냥 말 그대로 #include는 복사/붙여 넣기를 한다.
그래서 포함되는 라이브러리가 많아질수록 전처리 시간이 느려지는 것이다.
// 전처리.
// 아래 #include 구문은 iostream 헤더 파일을 포함시킨다.
#include <iostream>
#define A 10
using int32 = __int32;
// 프로그램의 진입점.
// 모든 프로그램은 진입점(EntryPoint)을 가진다.
// C++의 콘솔 기반 프로그램의 경우, 진입점 함수 이름을 main으로 정의한다.
int main()
{
std::cout << "Hello World!" << "\n";
}
컴파일 단계 - 어셈블리 단계
컴파일 단계는 전처리가 끝난 뒤, 어셈블리 코드 생성 이전에 이뤄지는 과정이다.
컴파일 단계에서 어셈블리 단계는 나뉘어 있긴 하지만, 그냥 어셈블리 코드로 변환하는 과정이다.
컴파일은 컴파일러가 처리하며, C++ 소스 파일 즉, .cpp 파일 단위로 컴파일을 진행한다.
여기서 using이나 typedef 같은 타입 별칭도 적용되어, 컴파일러 내부에서는 별칭이 실제 타입으로 치환된다.
또한, auto 키워드도 컴파일 시점에서 타입의 정의된다.
이 과정을 모두 마치면, 컴파일러는 .obj 파일을 생성하여 프로젝트 중간 폴더 경로에 저장한다.
(.obj 파일은 모든 .cpp에 대응되어 각자 하나씩 만들어짐)
// Main.cpp
#include <iostream>
int main()
{
std::cout << "Hello World" << "\n";
}

예시
다음 코드를 보자.
Main.cpp에서 Log.h의 함수를 호출하는 과정이다.
하지만, 다음 코드를 "빌드"하면 오류가 날 것이다.
Log.cpp에 Log 함수가 구현되지 않았기 때문이다.
하지만, Main.cpp을 Ctrl + F7을 통해, "컴파일"하면, 컴파일 자체는 오류가 나지 않고 정상적으로 컴파일된다.
이유는 컴파일 단계에서는 각 .cpp 파일만 검사하기 때문이다.
빌드 오류의 문제는 Log.cpp에서 Log 함수를 구현하지 않았기 때문이지, Main.cpp의 코드에서는 아무런 문제가 없기 때문이다.
// Main.cpp
#include <iostream>
#include "Log.h"
void Log(const char* message);
int main()
{
Log("Hello World");
}
// Log.h
#pragma once
void Log(const char* Message);
// Log.cpp
#include "Log.h"
#include <iostream>
// void Log(const char* message)
// {
// std::cout << message << std::endl;
// }
링크 단계
링크 단계에서는 링커가 링킹을 수행한다.
링킹은 컴파일한 중간 파일(.obj)들을 서로 연결하는 과정이다.
- 각종 심볼(symbol, 변수 등) 및 함수가 어디에 있는지 확인하고 서로 연결하는 과정
- cpp 파일이 obj 파일로 컴파일되면 cpu가 읽을 수 있는 translation unit으로 변환되는데 obj 파일 각각은 서로 연결 고리(관계)가 없기 때문에 다른 파일에 대한 정보를 알지 못한다.
- 이들을 서로 연결해 주는 과정을 링킹(Linking)이라고 하고, 이 작업은 링커(Linker)가 담당한다.
앞선 컴파일 단계에서의 문제를 예시로 들어보자.
- C++는 [컴파일 + 링크] 단계를 거친다.
- 오류가 발생했을 때는 두 단계에서 서로 다른 오류 메시지를 출력한다.
Log 헤더에서 Log 함수가 선언은 되어있어서 Main 함수의 컴파일은 성공했지만, 구현부가 없기 때문에 링크 단계에서 오류가 발생하여 빌드에 실패했다.
따라서 Log.cpp에 구현부를 작성해 주면 링크 오류가 해결되어 빌드가 성공하게 된다.
// Log.cpp
#include "Log.h"
#include <iostream>
void Log(const char* message)
{
std::cout << message << std::endl;
}
또한, 함수의 본문이 여러 번 정의돼 있어도 링크 과정에서 문제가 발생한다.
#include <iostream>
void Log(const char* message);
void Log(const char* message)
{
std::cout << message << std::endl;
}
int Multiply(int a, int b)
{
Log("Multiply");
return a * b;
}
int main()
{
std::cout << Multiply(5, 8) << std::endl;
}
'📕Programming > 📝C/C++' 카테고리의 다른 글
| [C / C++] 정적 라이브러리 (static library) (0) | 2025.07.18 |
|---|---|
| [C / C++] 메모리 누수 찾기 (0) | 2025.07.16 |
| [C / C++] 인라인 함수 (in-line function) (0) | 2023.12.28 |
| [C / C++] 스마트 포인터(Smart Pointer) (0) | 2023.12.06 |
| [C / C++] 완벽 전달(Perfect Forwarding)과 std::forward (0) | 2023.12.05 |