icon

컴파일과 실행 과정


C++ 프로그램의 생명 주기

 C++ 프로그램이 소스 코드에서 실행 가능한 프로그램이 되기까지의 과정을 생명 주기라고 합니다.

 이 과정은 다음과 같은 단계로 구성됩니다.

소스 코드 작성

전처리 (Preprocessing)

컴파일 (Compilation)

어셈블 (Assembly)

링킹 (Linking)

로딩 (Loading)

실행 (Execution)

 각 단계를 자세히 살펴보겠습니다.

전처리 과정

 전처리기는 컴파일 전에 소스 코드를 수정하는 역할을 합니다.

 주요 작업은 다음과 같습니다.

주석 제거

#include 지시문 처리

 헤더 파일의 내용을 소스 코드에 삽입

매크로 확장

 #define으로 정의된 매크로를 실제 값으로 대체

조건부 컴파일

 #ifdef, #ifndef, #endif 등의 지시문 처리

예시
#define PI 3.14159
#define SQUARE(x) ((x) * (x))
 
double area = PI * SQUARE(radius);
전처리 후
double area = 3.14159 * ((radius) * (radius));
전처리 결과를 확인하는 명령어 (GCC 기준)
g++ -E sourcefile.cpp

컴파일 과정

 컴파일러는 전처리된 소스 코드를 어셈블리 코드로 변환합니다.

 이 과정에서 다음과 같은 작업이 수행됩니다.

문법 검사

 코드의 문법적 오류를 찾아냅니다.

의미 분석

 변수 타입 체크, 함수 호출의 유효성 검사 등을 수행합니다.

중간 코드 생성

 소스 코드를 컴파일러 내부의 중간 표현으로 변환합니다.

코드 최적화

 실행 속도를 높이거나 코드 크기를 줄이는 최적화를 수행합니다.

목적 코드 생성

 최종적으로 어셈블리 코드를 생성합니다.

 주요 C++ 컴파일러

  • GCC (GNU Compiler Collection)
  • Clang
  • Microsoft Visual C++ Compiler
어셈블리 코드 생성 명령어 (GCC 기준)
g++ -S sourcefile.cpp

어셈블 과정

 어셈블러는 어셈블리 코드를 기계어로 변환합니다. 이 과정의 결과물을 목적 파일(object file)이라고 하며, 확장자는 보통 .o (Unix) 또는 .obj (Windows)입니다.

목적 파일 생성 명령어 (GCC 기준)
g++ -c sourcefile.cpp

링킹 과정

 링커는 여러 목적 파일과 라이브러리를 하나의 실행 파일로 결합합니다.

 주요 작업은 다음과 같습니다.

심볼 해결

 외부 참조를 실제 메모리 주소로 연결합니다.

메모리 주소 할당

 각 섹션(코드, 데이터 등)에 메모리 주소를 할당합니다.

재배치

 코드와 데이터의 주소를 조정합니다.

 링킹의 종류

  • 정적 링킹 : 라이브러리 코드를 실행 파일에 포함시킵니다. 실행 파일의 크기가 커지지만, 배포가 간편합니다.
  • 동적 링킹 : 라이브러리를 별도로 유지하고, 실행 시점에 연결합니다. 실행 파일의 크기는 작아지지만, 배포 시 라이브러리도 함께 제공해야 합니다.

실행 파일 구조

 실행 파일은 다음과 같은 주요 섹션으로 구성됩니다.

텍스트 섹션

 실행 가능한 코드

데이터 섹션

 초기화된 전역 변수와 정적 변수

BSS 섹션

 초기화되지 않은 전역 변수와 정적 변수

 동적으로 할당되는 메모리 영역

스택

 지역 변수와 함수 호출 정보를 저장하는 영역

실행 파일 구조 확인 명령어 (Linux)
objdump -h executable

컴파일 명령어 상세 분석

GCC 컴파일 명령어 예시
g++ -Wall -O2 -std=c++17 -c main.cpp -o main.o
g++ main.o -o myprogram

 옵션 설명

  • -Wall : 모든 경고 메시지를 출력합니다.
  • -O2 : 최적화 레벨 2를 적용합니다. (0부터 3까지 있으며, 숫자가 클수록 더 강력한 최적화를 수행)
  • -std=c++17 : C++17 표준을 사용합니다.
  • -c : 컴파일만 수행하고 링크는 하지 않습니다.
  • -o : 출력 파일의 이름을 지정합니다.

빌드 자동화 도구

 대규모 프로젝트에서는 빌드 과정을 자동화하기 위해 다음과 같은 도구들을 사용합니다.

Make

 Makefile을 사용하여 빌드 과정을 정의하고 실행합니다.

Makefile 예시
CXX = g++
CXXFLAGS = -Wall -O2 -std=c++17
 
myprogram: main.o helper.o
    $(CXX) $(CXXFLAGS) main.o helper.o -o myprogram
 
main.o: main.cpp
    $(CXX) $(CXXFLAGS) -c main.cpp
 
helper.o: helper.cpp helper.h
    $(CXX) $(CXXFLAGS) -c helper.cpp
 
clean:
    rm -f *.o myprogram

CMake

 크로스 플랫폼 빌드 시스템으로, 다양한 환경에서 일관된 빌드 스크립트를 사용할 수 있게 해줍니다.

Ninja

 빠른 빌드 속도에 중점을 둔 빌드 시스템입니다.

연습 문제

  1. 다음 코드의 전처리 결과를 예측해보세요.
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SQUARE(x) ((x) * (x))
 
int main() {
    int x = 5, y = 10;
    int result = MAX(x, y) + SQUARE(3);
    return 0;
}
  1. 컴파일러 최적화 레벨에 따른 실행 속도 차이를 측정하는 프로그램을 작성하고, -O0, -O1, -O2, -O3 옵션으로 각각 컴파일하여 결과를 비교해보세요.
  2. 여러 개의 소스 파일을 사용하는 간단한 프로젝트를 만들고, 이를 빌드하는 Makefile을 작성해보세요.
  3. 동적 링킹과 정적 링킹의 차이점을 설명하고, 각각의 장단점을 논의해보세요.

 참고자료