C++ 컴파일 과정 | 전처리·컴파일·어셈블·링킹 완벽 가이드
이 글의 핵심
C++ 컴파일 과정 완벽 가이드. 소스 코드에서 실행 파일까지 전처리 → 컴파일 → 어셈블 → 링킹 4단계를 상세히 설명합니다. name mangling, 심볼 해결, 최적화 옵션까지 다룹니다.
들어가며
C++ 컴파일 과정은 소스 코드에서 실행 파일까지 전처리 → 컴파일 → 어셈블 → 링킹 4단계로 이루어집니다.
비유로 말씀드리면, 전처리는 레시피 준비 (재료 모으기), 컴파일은 요리 (재료를 음식으로), 어셈블은 포장 (음식을 용기에), 링킹은 배달 (여러 요리를 한 세트로)에 가깝습니다.
이 글을 읽으면
- 컴파일 4단계를 이해합니다
- 각 단계의 역할과 산출물을 파악합니다
- 컴파일러 옵션과 최적화를 익힙니다
- 링크 에러와 전처리 문제를 해결합니다
목차
컴파일 4단계
전체 흐름
graph LR
A[소스 코드<br/>.cpp] --> B[전처리<br/>.i]
B --> C[컴파일<br/>.s]
C --> D[어셈블<br/>.o]
D --> E[링킹<br/>실행 파일]
단계별 설명
| 단계 | 입력 | 출력 | 역할 |
|---|---|---|---|
| 전처리 | .cpp | .i | #include, #define 처리 |
| 컴파일 | .i | .s | C++ → 어셈블리 |
| 어셈블 | .s | .o | 어셈블리 → 기계어 |
| 링킹 | .o | 실행 파일 | 오브젝트 파일 결합 |
실전 구현
1) 전처리 (Preprocessing)
// main.cpp
#include <iostream>
#define PI 3.14159
#define SQUARE(x) ((x) * (x))
int main() {
std::cout << "PI: " << PI << std::endl;
std::cout << "SQUARE(5): " << SQUARE(5) << std::endl;
return 0;
}
# 전처리 결과 확인
g++ -E main.cpp -o main.i
main.i (일부):
// ... iostream 내용 전체 ...
int main() {
std::cout << "PI: " << 3.14159 << std::endl;
std::cout << "SQUARE(5): " << ((5) * (5)) << std::endl;
return 0;
}
2) 컴파일 (Compilation)
# C++를 어셈블리로
g++ -S main.i -o main.s
main.s (일부):
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov edi, OFFSET FLAT:_ZSt4cout
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
; ...
ret
3) 어셈블 (Assembly)
# 어셈블리를 기계어로
g++ -c main.s -o main.o
main.o: 바이너리 파일 (기계어)
# 오브젝트 파일 확인 (Linux)
objdump -d main.o
# 심볼 확인
nm main.o
4) 링킹 (Linking)
# 오브젝트 파일을 실행 파일로
g++ main.o -o main
# 실행
./main
전체 과정 한 번에
# 4단계를 한 번에
g++ main.cpp -o main
# 단계별 (학습용)
g++ -E main.cpp -o main.i # 전처리
g++ -S main.i -o main.s # 컴파일
g++ -c main.s -o main.o # 어셈블
g++ main.o -o main # 링킹
고급 활용
1) 여러 파일 컴파일
// util.h
#ifndef UTIL_H
#define UTIL_H
int add(int a, int b);
#endif
// util.cpp
#include "util.h"
int add(int a, int b) {
return a + b;
}
// main.cpp
#include <iostream>
#include "util.h"
int main() {
std::cout << add(3, 5) << std::endl;
return 0;
}
# 각각 컴파일
g++ -c main.cpp -o main.o
g++ -c util.cpp -o util.o
# 링킹
g++ main.o util.o -o myapp
# 실행
./myapp
2) 라이브러리 링크
정적 라이브러리 (.a)
# 정적 라이브러리 생성
ar rcs libmylib.a util.o
# 링크
g++ main.o -L. -lmylib -o myapp
동적 라이브러리 (.so / .dll)
# 동적 라이브러리 생성 (Linux)
g++ -shared -fPIC util.cpp -o libmylib.so
# 링크
g++ main.o -L. -lmylib -Wl,-rpath,. -o myapp
# Windows
g++ -shared util.cpp -o mylib.dll
g++ main.o -L. -lmylib -o myapp.exe
3) 최적화 옵션
# -O0: 최적화 없음 (디버그)
g++ -O0 -g main.cpp -o main_debug
# -O1: 기본 최적화
g++ -O1 main.cpp -o main_o1
# -O2: 권장 최적화 (릴리스)
g++ -O2 main.cpp -o main_o2
# -O3: 최대 최적화
g++ -O3 main.cpp -o main_o3
# -Os: 크기 최적화
g++ -Os main.cpp -o main_os
4) 경고 옵션
# 기본 경고
g++ -Wall main.cpp
# 추가 경고
g++ -Wall -Wextra main.cpp
# 경고를 에러로
g++ -Wall -Wextra -Werror main.cpp
# 특정 경고 비활성화
g++ -Wall -Wno-unused-variable main.cpp
성능 비교
최적화 레벨 비교
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec(1000000);
for (int i = 0; i < 1000000; ++i) {
vec[i] = i * 2;
}
long long sum = 0;
for (int x : vec) {
sum += x;
}
std::cout << sum << std::endl;
return 0;
}
컴파일 및 실행:
# -O0
g++ -O0 main.cpp -o main_o0
time ./main_o0
# -O2
g++ -O2 main.cpp -o main_o2
time ./main_o2
# -O3
g++ -O3 main.cpp -o main_o3
time ./main_o3
결과 (GCC 13):
| 옵션 | 실행 시간 | 바이너리 크기 |
|---|---|---|
| -O0 | 150ms | 18KB |
| -O1 | 50ms | 16KB |
| -O2 | 20ms | 16KB |
| -O3 | 15ms | 20KB |
분석: -O2가 성능과 크기의 균형
실무 사례
사례 1: 프로젝트 빌드 스크립트
#!/bin/bash
# 변수 설정
CXX=g++
CXXFLAGS="-std=c++17 -Wall -Wextra -O2"
SOURCES="main.cpp util.cpp math.cpp"
OBJECTS="main.o util.o math.o"
TARGET="myapp"
# 컴파일
for src in $SOURCES; do
$CXX $CXXFLAGS -c $src
done
# 링킹
$CXX $OBJECTS -o $TARGET
# 실행
./$TARGET
사례 2: Makefile
CXX = g++
CXXFLAGS = -std=c++17 -Wall -Wextra -O2
LDFLAGS = -L./lib -lmylib
SOURCES = main.cpp util.cpp math.cpp
OBJECTS = $(SOURCES:.cpp=.o)
TARGET = myapp
all: $(TARGET)
$(TARGET): $(OBJECTS)
$(CXX) $(OBJECTS) $(LDFLAGS) -o $(TARGET)
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@
clean:
rm -f $(OBJECTS) $(TARGET)
.PHONY: all clean
사용:
# 빌드
make
# 클린
make clean
# 재빌드
make clean && make
사례 3: CMake
cmake_minimum_required(VERSION 3.10)
project(MyApp)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
add_executable(myapp
main.cpp
util.cpp
math.cpp
)
target_link_libraries(myapp mylib)
사용:
# 빌드 디렉토리 생성
mkdir build
cd build
# CMake 실행
cmake ..
# 빌드
make
# 실행
./myapp
사례 4: 조건부 컴파일
// config.h
#ifndef CONFIG_H
#define CONFIG_H
#ifdef DEBUG
#define LOG(x) std::cout << "[DEBUG] " << x << std::endl
#else
#define LOG(x)
#endif
#ifdef ENABLE_FEATURE_X
#define FEATURE_X_ENABLED
#endif
#endif
// main.cpp
#include <iostream>
#include "config.h"
int main() {
LOG("프로그램 시작");
#ifdef FEATURE_X_ENABLED
std::cout << "Feature X 활성화" << std::endl;
#endif
return 0;
}
컴파일:
# DEBUG 모드
g++ -DDEBUG main.cpp -o main_debug
# RELEASE 모드
g++ main.cpp -o main_release
# Feature X 활성화
g++ -DENABLE_FEATURE_X main.cpp -o main_feature_x
트러블슈팅
문제 1: 헤더 미포함
증상: 컴파일 에러
// ❌ 헤더 없음
int main() {
std::cout << "Hello"; // 에러: 'cout' was not declared
}
// ✅ 헤더 포함
#include <iostream>
int main() {
std::cout << "Hello";
}
문제 2: 링크 에러 - undefined reference
증상: 링크 에러
// util.h
int add(int a, int b);
// main.cpp
#include <iostream>
#include "util.h"
int main() {
std::cout << add(3, 5) << std::endl; // 링크 에러
return 0;
}
에러:
undefined reference to `add(int, int)'
해결:
// util.cpp 추가
#include "util.h"
int add(int a, int b) {
return a + b;
}
# 컴파일 및 링크
g++ -c main.cpp -o main.o
g++ -c util.cpp -o util.o
g++ main.o util.o -o myapp
문제 3: 중복 정의
증상: 링크 에러
// header.h
int globalVar = 10; // ❌ 중복 정의 (여러 .cpp에서 include 시)
// ✅ 선언만
extern int globalVar;
// source.cpp
int globalVar = 10; // 정의
문제 4: 순환 의존성
증상: 컴파일 에러
// a.h
#ifndef A_H
#define A_H
#include "b.h"
class A {
B* b; // B 사용
};
#endif
// b.h
#ifndef B_H
#define B_H
#include "a.h" // 순환
class B {
A* a; // A 사용
};
#endif
해결: 전방 선언
// a.h
#ifndef A_H
#define A_H
class B; // 전방 선언
class A {
B* b;
};
#endif
// b.h
#ifndef B_H
#define B_H
class A; // 전방 선언
class B {
A* a;
};
#endif
문제 5: 인클루드 경로
증상: 헤더 파일을 찾을 수 없음
# 에러
g++ main.cpp -o main
# fatal error: myheader.h: No such file or directory
# 해결: -I 옵션으로 인클루드 경로 추가
g++ -I./include main.cpp -o main
문제 6: 라이브러리 경로
증상: 라이브러리를 찾을 수 없음
# 에러
g++ main.o -lmylib -o myapp
# /usr/bin/ld: cannot find -lmylib
# 해결: -L 옵션으로 라이브러리 경로 추가
g++ main.o -L./lib -lmylib -o myapp
마무리
C++ 컴파일 과정은 전처리 → 컴파일 → 어셈블 → 링킹 4단계로 이루어집니다.
핵심 요약
-
컴파일 4단계
- 전처리: #include, #define 처리
- 컴파일: C++ → 어셈블리
- 어셈블: 어셈블리 → 기계어
- 링킹: 오브젝트 파일 결합
-
파일 확장자
- .cpp: 소스 코드
- .i: 전처리 결과
- .s: 어셈블리
- .o: 오브젝트 파일
- 실행 파일: a.out (Linux), .exe (Windows)
-
최적화 옵션
- -O0: 디버그
- -O1: 기본
- -O2: 권장 (릴리스)
- -O3: 최대
-
링크 에러
- 함수 구현 누락
- 라이브러리 미링크
- 심볼 미정의
컴파일러 옵션 치트시트
# 표준 지정
g++ -std=c++17 main.cpp
# 경고
g++ -Wall -Wextra -Werror main.cpp
# 최적화
g++ -O2 main.cpp
# 디버그
g++ -g main.cpp
# 전처리 정의
g++ -DDEBUG main.cpp
# 인클루드 경로
g++ -I./include main.cpp
# 라이브러리 경로
g++ main.o -L./lib -lmylib -o myapp
# 동적 라이브러리 경로 (Linux)
g++ main.o -L./lib -lmylib -Wl,-rpath,./lib -o myapp
다음 단계
- 링킹: C++ 링킹
- Name Mangling: C++ Name Mangling
- Makefile: C++ Makefile
- CMake: C++ CMake
참고 자료
- “Linkers and Loaders” - John R. Levine
- GCC 문서: https://gcc.gnu.org/onlinedocs/
- “C++ Primer” - Stanley Lippman
한 줄 정리: C++ 컴파일은 전처리·컴파일·어셈블·링킹 4단계로 이루어지며, 각 단계를 이해하면 빌드 에러를 효과적으로 해결할 수 있다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Linking | “링킹” 가이드
- C++ Name Mangling | “이름 맹글링” 가이드
- C++ Makefile | “Make 빌드” 가이드
- C++ Include Path | “인클루드 경로” 가이드
관련 글
- C++ CMake |
- C++ Linking |
- C++ Macro Programming |
- C++ Name Mangling |
- C++ One Definition Rule |