[2026] C++ 컴파일 과정 | 전처리·컴파일·어셈블·링킹 완벽 가이드

[2026] C++ 컴파일 과정 | 전처리·컴파일·어셈블·링킹 완벽 가이드

이 글의 핵심

C++ 소스 코드에서 실행 파일까지 전처리 → 컴파일 → 어셈블 → 링킹 4단계입니다. name mangling은 컴파일 단계에서, 심볼 해결은 링킹 단계에서 이루어지며, Makefile·인클루드 경로는 이 과정을 자동화할 때 씁니다.

들어가며

C++ 컴파일 과정소스 코드에서 실행 파일까지 전처리 → 컴파일 → 어셈블 → 링킹 4단계로 이루어집니다. 비유로 말씀드리면, 전처리레시피 준비 (재료 모으기), 컴파일요리 (재료를 음식으로), 어셈블포장 (음식을 용기에), 링킹배달 (여러 요리를 한 세트로)에 가깝습니다.

이 글을 읽으면

  • 컴파일 4단계를 이해합니다
  • 각 단계의 역할과 산출물을 파악합니다
  • 컴파일러 옵션과 최적화를 익힙니다
  • 링크 에러와 전처리 문제를 해결합니다

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

목차

  1. 컴파일 4단계
  2. 실전 구현
  3. 고급 활용
  4. 성능 비교
  5. 실무 사례
  6. 트러블슈팅
  7. 마무리

컴파일 4단계

전체 흐름

아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

graph LR
    A["소스 코드\n.cpp"] --> B["전처리\n.i"]
    B --> C["컴파일\n.s"]
    C --> D["어셈블\n.o"]
    D --> E["링킹\n실행 파일"]

단계별 설명

단계입력출력역할
전처리.cpp.i#include, #define 처리
컴파일.i.sC++ → 어셈블리
어셈블.s.o어셈블리 → 기계어
링킹.o실행 파일오브젝트 파일 결합

실전 구현

1) 전처리 (Preprocessing)

아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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 (일부): 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ....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 (일부): 아래 코드는 asm를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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: 바이너리 파일 (기계어) 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 오브젝트 파일 확인 (Linux)
objdump -d main.o
# 심볼 확인
nm main.o

4) 링킹 (Linking)

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 오브젝트 파일을 실행 파일로
g++ main.o -o main
# 실행
./main

전체 과정 한 번에

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 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) 여러 파일 컴파일

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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;
}

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 각각 컴파일
g++ -c main.cpp -o main.o
g++ -c util.cpp -o util.o
# 링킹
g++ main.o util.o -o myapp
# 실행
./myapp

2) 라이브러리 링크

정적 라이브러리 (.a)

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 정적 라이브러리 생성
ar rcs libmylib.a util.o
# 링크
g++ main.o -L. -lmylib -o myapp

동적 라이브러리 (.so / .dll)

아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 동적 라이브러리 생성 (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) 최적화 옵션

아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# -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) 경고 옵션

아래 코드는 bash를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 기본 경고
g++ -Wall main.cpp
# 추가 경고
g++ -Wall -Wextra main.cpp
# 경고를 에러로
g++ -Wall -Wextra -Werror main.cpp
# 특정 경고 비활성화
g++ -Wall -Wno-unused-variable main.cpp

성능 비교

최적화 레벨 비교

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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;
}

컴파일 및 실행: 아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# -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):

옵션실행 시간바이너리 크기
-O0150ms18KB
-O150ms16KB
-O220ms16KB
-O315ms20KB
분석: -O2가 성능과 크기의 균형

실무 사례

사례 1: 프로젝트 빌드 스크립트

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#!/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

다음은 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

사용: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 빌드
make
# 클린
make clean
# 재빌드
make clean && make

사례 3: CMake

아래 코드는 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)

사용: 아래 코드는 bash를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 빌드 디렉토리 생성
mkdir build
cd build
# CMake 실행
cmake ..
# 빌드
make
# 실행
./myapp

사례 4: 조건부 컴파일

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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;
}

컴파일: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 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: 헤더 미포함

증상: 컴파일 에러 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 헤더 없음
int main() {
    std::cout << "Hello";  // 에러: 'cout' was not declared
}
// ✅ 헤더 포함
#include <iostream>
int main() {
    std::cout << "Hello";
}

문제 2: 링크 에러 - undefined reference

증상: 링크 에러 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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)'

해결: 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

// util.cpp 추가
#include "util.h"
int add(int a, int b) {
    return a + b;
}

다음은 간단한 bash 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 컴파일 및 링크
g++ -c main.cpp -o main.o
g++ -c util.cpp -o util.o
g++ main.o util.o -o myapp

문제 3: 중복 정의

증상: 링크 에러 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// header.h
int globalVar = 10;  // ❌ 중복 정의 (여러 .cpp에서 include 시)
// ✅ 선언만
extern int globalVar;
// source.cpp
int globalVar = 10;  // 정의

문제 4: 순환 의존성

증상: 컴파일 에러 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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

해결: 전방 선언 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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: 인클루드 경로

증상: 헤더 파일을 찾을 수 없음 아래 코드는 bash를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 에러
g++ main.cpp -o main
# fatal error: myheader.h: No such file or directory
# 해결: -I 옵션으로 인클루드 경로 추가
g++ -I./include main.cpp -o main

문제 6: 라이브러리 경로

증상: 라이브러리를 찾을 수 없음 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 에러
g++ main.o -lmylib -o myapp
# /usr/bin/ld: cannot find -lmylib
# 해결: -L 옵션으로 라이브러리 경로 추가
g++ main.o -L./lib -lmylib -o myapp

마무리

C++ 컴파일 과정전처리 → 컴파일 → 어셈블 → 링킹 4단계로 이루어집니다.

핵심 요약

  1. 컴파일 4단계
    • 전처리: #include, #define 처리
    • 컴파일: C++ → 어셈블리
    • 어셈블: 어셈블리 → 기계어
    • 링킹: 오브젝트 파일 결합
  2. 파일 확장자
    • .cpp: 소스 코드
    • .i: 전처리 결과
    • .s: 어셈블리
    • .o: 오브젝트 파일
    • 실행 파일: a.out (Linux), .exe (Windows)
  3. 최적화 옵션
    • -O0: 디버그
    • -O1: 기본
    • -O2: 권장 (릴리스)
    • -O3: 최대
  4. 링크 에러
    • 함수 구현 누락
    • 라이브러리 미링크
    • 심볼 미정의

컴파일러 옵션 치트시트

다음은 bash를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 표준 지정
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

다음 단계

참고 자료

  • “Linkers and Loaders” - John R. Levine
  • GCC 문서: https://gcc.gnu.org/onlinedocs/
  • “C++ Primer” - Stanley Lippman 한 줄 정리: C++ 컴파일은 전처리·컴파일·어셈블·링킹 4단계로 이루어지며, 각 단계를 이해하면 빌드 에러를 효과적으로 해결할 수 있다.

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3