C++ 코드를 실행하는 과정
이미지 출처: Markus Spiske on Unsplash
1. 전처리
우선 #include
구문을 지정한 파일에 담긴 내용으로 대체합니다.
꺽쇠(<...>
)를 사용해 경로를 지정한 경우 전처리기는 표준 라이브러리가 있는 시스템 디렉토리부터 사용자가 설정한 디렉토리를 차례로 들여다봅니다. 디렉토리는 VS Studio 2022를 사용할 경우 Properties > C/C++ > General > Additinal Include Directories에서 설정할 수 있습니다.
2. 컴파일
전처리한 코드를 어셈블리 코드(중간 언어)로 변환합니다. 프론트엔드 컴파일러가 이 작업을 수행합니다.
그런데 왜 전처리한 코드를 바로 기계어로 변환하지 않을까요?
몇 가지 이점 때문입니다.
- 프론트엔드 컴파일러는 고급 언어를 최적화하는 데에 집중할 수 있습니다.
- 고급 언어에 대한 최적화(inlining, loop unrolling, constant propagation...)는 기계어보다 인간이 읽을 수 있는 어셈블리 코드로 구현하기가 편합니다.
- 백엔드 컴파일러는 특정한 기계에 맞춘 코드를 생성해내는 데에 집중할 수 있습니다.
- 프로그램이 기계 수준에서 작동하는 모습을 읽어 볼 수 있습니다.
3. 어셈블
어셈블리 코드를 오브젝트 파일로 변환합니다. 백엔드 컴파일러가 이 작업을 수행하죠.
하지만 오브젝트 파일은 바로 실행할 수 없습니다. 그 이유는 오브젝트 파일은 기계어를 포함하지만 실행에 필요한 스타트업 코드, 표준 라이브러리 코드 등을 포함하지 않기 때문입니다.
그도 그럴 것이 오브젝트 파일은 c++ 파일을 따로따로 변환한 결과물입니다. 그러므로 실행 가능한 파일을 생성하기 위해 모든 파일을 연결하는 과정이 필요합니다. 이 과정을 다음 단계로서 링킹이라고 부릅니다.
4. 링킹
링킹은 단순하게 말해서 실행에 필요한 오브젝트를 파일을 연결하는 단계입니다. 이 단계가 끝나면 운영체제가 실행할 수 있는 파일이 나옵니다.
앞서 #include
를 통해 불러왔지만 정의 없이 선언부만 가진 대상들(function, class ...)이 있습니다. 이러한 대상들의 구현을 찾아 연결해 주는 과정이 링킹입니다.
이 과정에서도 링커는 우선 시스템 디렉토리부터 사용자가 설정한 디렉토리를 차례로 들여다봅니다. 디렉토리는 VS Studio를 사용할 경우 Properties > Linker > General > Additinal Library Directories에서 설정할 수 있습니다. 그리고 특정한 라이브러리 파일을 Properties > Linker > Input > Additinal Dependencies에서 지정할 수 있어요.
이때 구현을 연결하는 방식에 따라 정적 링킹과 동적 링킹으로 나뉩니다.
정적 링킹
정적 링킹은 컴파일 시간에 라이브러리 파일을 불러오는 방법입니다.
라이브러리는 단순하게 말해서 여러 오브젝트 파일을 합친 파일입니다. 윈도우 운영체제에서는 .lib
확장자를 가지고 있죠.
동적 링킹
동적 링킹은 런타임 시간에 라이브러리 파일을 불러오는 방법입니다.
하지만 정적 링킹에서 사용하는 .lib
파일을 그대로 불러올 수는 없습니다. 정적 링킹과 다르게 메모리 주소를 미리 정할 수 없기 때문입니다. 그렇기 때문에 동적 링킹을 위한 라이브러리 파일을 따로 만듭니다. 확장자는 .dll
(dynamic link library)입니다.
컴파일 이후에 .dll
파일을 실행 파일과 같은 디렉토리에 넣어 주어야 합니다. VS Studio에서는 Properties > Build Events > Post-Build Event를 이용해 작업을 자동화할 수 있습니다.