C++ 프로그램의 빌드 과정은 크게 전처리(Preprocessing), 컴파일(Compilation), 링킹(Linking)의 세 단계로 나뉩니다. 각 단계가 어떤 역할을 담당하는지 하나씩 살펴보겠습니다.
1. 전처리(Preprocessing)
전처리기(C Preprocessor)는 간단히 말해 텍스트 치환 도구입니다. 실제 컴파일이 시작되기 전에 필요한 사전 작업을 수행하도록 컴파일러에 지시하는 역할을 합니다.
이 단계에서는 #include, #define 등의 전처리 지시문(directive)이 처리됩니다. 예를 들어 헤더 파일의 내용이 소스 코드에 그대로 삽입되고, 매크로가 실제 값으로 치환됩니다.
2. 컴파일(Compilation)
컴파일은 전처리가 완료된 파일을 대상으로 진행됩니다. 컴파일러는 순수한 C++ 소스 코드를 파싱(parsing)하여 어셈블리 코드로 변환하며, 이어서 어셈블러(assembler)가 호출되어 어셈블리 코드를 기계어(바이너리) 형태의 오브젝트 파일(Object file)로 만듭니다.
여기서 중요한 점은, 오브젝트 파일에는 아직 정의되지 않은 심볼(symbol)에 대한 참조가 포함될 수 있다는 것입니다. 소스 코드가 문법적으로 잘 형성되어 있지 않은 경우가 아니라면 컴파일러는 오류를 발생시키지 않습니다.
- 구문 오류(syntax error)
- 오버로드 해결 실패(overload resolution failure) 등
위와 같은 오류들은 모두 이 컴파일 단계에서 발생합니다. 또한 생성된 오브젝트 파일들은 필요에 따라 정적 라이브러리(static library)로 활용할 수도 있습니다.
3. 링킹(Linking)
링커(linker)는 컴파일러가 생성한 여러 오브젝트 파일로부터 최종 결과물을 만들어냅니다. 이 최종 출력물은 공유(동적) 라이브러리(shared/dynamic library) 또는 실행 파일(executable)이 될 수 있습니다.
링커는 오브젝트 파일에 남아 있던 정의되지 않은 참조(undefined reference)를 실제 주소로 교체하는 방식으로 오브젝트 파일들을 서로 연결합니다. 이때 해당 심볼들은 반드시 다른 오브젝트 파일이나 라이브러리 안에 정의되어 있어야 합니다.
만약 심볼이 표준 라이브러리가 아닌 외부 라이브러리에 정의되어 있다면, 컴파일/링크 시점에 해당 라이브러리를 명시적인 인자로 전달해야 링커가 이를 찾아 성공적으로 연결할 수 있습니다.
정리
즉, C/C++ 프로그램은 전처리 → 컴파일(어셈블 포함) → 링킹의 흐름을 거쳐 하나의 실행 가능한 바이너리로 완성됩니다. 각 단계의 역할을 정확히 이해하면 'undefined reference'와 같은 링크 오류나 컴파일 오류의 원인을 훨씬 쉽게 파악할 수 있습니다.