컴파일과 실행 과정
C++ 프로그램의 생명 주기
C++ 프로그램이 소스 코드에서 실행 가능한 프로그램이 되기까지의 과정을 생명 주기라고 합니다.
이 과정은 다음과 같은 단계로 구성됩니다.
각 단계를 자세히 살펴보겠습니다.
전처리 과정
전처리기는 컴파일 전에 소스 코드를 수정하는 역할을 합니다.
주요 작업은 다음과 같습니다.
주석 제거
#include
지시문 처리
헤더 파일의 내용을 소스 코드에 삽입
매크로 확장
#define
으로 정의된 매크로를 실제 값으로 대체
조건부 컴파일
#ifdef
, #ifndef
, #endif
등의 지시문 처리
컴파일 과정
컴파일러는 전처리된 소스 코드를 어셈블리 코드로 변환합니다.
이 과정에서 다음과 같은 작업이 수행됩니다.
문법 검사
코드의 문법적 오류를 찾아냅니다.
의미 분석
변수 타입 체크, 함수 호출의 유효성 검사 등을 수행합니다.
중간 코드 생성
소스 코드를 컴파일러 내부의 중간 표현으로 변환합니다.
코드 최적화
실행 속도를 높이거나 코드 크기를 줄이는 최적화를 수행합니다.
목적 코드 생성
최종적으로 어셈블리 코드를 생성합니다.
주요 C++ 컴파일러
- GCC (GNU Compiler Collection)
- Clang
- Microsoft Visual C++ Compiler
어셈블 과정
어셈블러는 어셈블리 코드를 기계어로 변환합니다. 이 과정의 결과물을 목적 파일(object file)이라고 하며, 확장자는 보통 .o (Unix) 또는 .obj (Windows)입니다.
링킹 과정
링커는 여러 목적 파일과 라이브러리를 하나의 실행 파일로 결합합니다.
주요 작업은 다음과 같습니다.
링킹의 종류
- 정적 링킹 : 라이브러리 코드를 실행 파일에 포함시킵니다. 실행 파일의 크기가 커지지만, 배포가 간편합니다.
- 동적 링킹 : 라이브러리를 별도로 유지하고, 실행 시점에 연결합니다. 실행 파일의 크기는 작아지지만, 배포 시 라이브러리도 함께 제공해야 합니다.
실행 파일 구조
실행 파일은 다음과 같은 주요 섹션으로 구성됩니다.
텍스트 섹션
실행 가능한 코드
데이터 섹션
초기화된 전역 변수와 정적 변수
BSS 섹션
초기화되지 않은 전역 변수와 정적 변수
힙
동적으로 할당되는 메모리 영역
스택
지역 변수와 함수 호출 정보를 저장하는 영역
컴파일 명령어 상세 분석
옵션 설명
-Wall
: 모든 경고 메시지를 출력합니다.-O2
: 최적화 레벨 2를 적용합니다. (0부터 3까지 있으며, 숫자가 클수록 더 강력한 최적화를 수행)-std=c++17
: C++17 표준을 사용합니다.-c
: 컴파일만 수행하고 링크는 하지 않습니다.-o
: 출력 파일의 이름을 지정합니다.
빌드 자동화 도구
대규모 프로젝트에서는 빌드 과정을 자동화하기 위해 다음과 같은 도구들을 사용합니다.
Make
Makefile을 사용하여 빌드 과정을 정의하고 실행합니다.
CMake
크로스 플랫폼 빌드 시스템으로, 다양한 환경에서 일관된 빌드 스크립트를 사용할 수 있게 해줍니다.
Ninja
빠른 빌드 속도에 중점을 둔 빌드 시스템입니다.
연습 문제
- 다음 코드의 전처리 결과를 예측해보세요.
- 컴파일러 최적화 레벨에 따른 실행 속도 차이를 측정하는 프로그램을 작성하고, -O0, -O1, -O2, -O3 옵션으로 각각 컴파일하여 결과를 비교해보세요.
- 여러 개의 소스 파일을 사용하는 간단한 프로젝트를 만들고, 이를 빌드하는 Makefile을 작성해보세요.
- 동적 링킹과 정적 링킹의 차이점을 설명하고, 각각의 장단점을 논의해보세요.
참고자료
- "컴파일러 : 원리, 기법, 도구" (앨프레드 V. 에이호 외 저)
- GCC 공식 문서 : https://gcc.gnu.org/onlinedocs/
- LLVM 문서 : https://llvm.org/docs/
- "링커와 로더" (존 R. 레벤)