C++
컴파일 과정
농산물
2022. 7. 11. 03:38
컴파일 과정
- 컴파일의 정의
- 컴파일은 인간이 이해 할 수 있는 언어로 작성된 소스 코드(고수준 언어: C, C++, Java 등등)를 CPU가 이해할 수 있는 언어(저수준 언어: 기계어)로 번역하는 작업을 말한다.
- 컴파일 과정 단계
- 1. 먼저 #include 와 #define과 같은 전처리기 매크로들을 처리하는 전처리(Preprocessing) 단계
- 2. 그뒤에 각각의 소스 파일들을 어셈블리 명령어로 변환하는 컴파일(Compile) 단계
- 3. 그후 어셈블리 코드들을 실제 기계어로 이루어진 목적 코드(Object file)로 변환 하는 어셈블리(Assemble)단계
- 4. 마지막으로 각각의 목적 코드들을 한데 모아서 하나의 실행 파일로 만들어주는 링킹(Linking)단계로 나누어 볼 수 있다.
- 전처리 단계
- 전처리 단계는 전처리기를 통해 소스 코드 파일(*.cpp)을 전처리된 소스 코드 파일(*.i)로 변환하는 과정
- 이 과정에서 수행하는 역할은
- 주석 제거: 소스 코드에서 주석을 전부 제거한다.
- 문자들 해석하기: 소스파일에 있는 문자들을 해석한다. 기본적으로 C++ 코드에서는 총 96개의 문자들로 이루어진 Basic source character set이 있는데, 이들은
- 5 종류의 공백 문자들(스페이스, 탭, 개행 문자 등등)
- 10 종류의 숫자들(0~9)
- 52종류의 알파벳 대소문자
- 29 종류의 특수 문자들
- 로 구성되어 있다. 이 기본 문자 셋에 포함되어 있지 않은 다른 모든 문자들은 유니코드 값으로 치환 되거나 컴파일러에 의해서 따로 해석된다.
- \문자 해석하기: 만약 백슬래시( \ )문자가 문장 맨 끝 부분에 위치해 있다면, 해당 문장과 바로 다음에 오는 문장이 하나로 합쳐지고 개행 문자는 삭제된다.
- 헤더 파일 삽입: #include 지시문을 만나면 해당하는 헤더 파일을 찾아 헤더 파일에 있는 모든 내용을 복사해서 소스 코드에 삽입한다. 즉, 헤더파일은 컴파일에 사용되지 않고 소스 코드 파일 내에 전부 복사된다. 헤더 파일에 선언된 함수 원형은 후에 링킹 과정을 통해 실제로 함수가 정의되어 있는 오브젝트 파일(컴파일된 소스 코드 파일)과 결합한다.
- 매크로 치환 및 적용:#define 지시문에 정의된 매크로를 저장하고 같은 문자열을 만나면 #define된 내용으로 치환한다. 간단히 매크로 이름을 찾아서 정의한 값으로 전부 바꿔준다.
- 인접한 문자열 합치기: 이 단계에서 인접한 문자열들이 하나로 합쳐진다. 에를들어
std::cout << "abc"
"def";
의 경우
std::cout << "abcdef";
로 변경된다.
- 컴파일 단계
- 컴파일 단계는 컴파일러(Compiler)를 통해 전처리된 소스 코드 파일(*.i)을 어셈블리어 파일(*.s)로 변환하는 과정이다.
- 이 과정에서 우리가 일반적으로 컴파일 하면 생각하는 언어의 문법 검사가 이루어진다. 전처리 토큰들이 컴파일 토큰으로 변환이 되고, 컴파일 토큰들은 컴파일러에 의해 해석되어 해석유닛을 생성한다(Translation Unit -TU) 이 해석 유닛은 소스파일 별로 하나 씩 존재하게 된다. 또한 Static한 영역들의 메모리 할당을 수행한다.
- 프론트 엔드 단계
- 프론트 엔드에서는 언어 종속적인 부분을 처리한다.
- 소스 코드가 해당 언어로 올바르게 작성되었는지 확인하고 미들 엔드에게 넘겨주기 위한 GIMPLE 트리를 생성한다.
- 미들 엔드 단계
- 미들엔드에서는 아키텍쳐 비종속적인 최적화를 수행한다.
- 아키텍쳐 비종속적인 최적화란 CPU 아키텍쳐가 무엇이든(arm, x86)등 상관없이 할 수 있는 최적화를 말한다.
- GIMPLE트리를 이용해 아키텍쳐 비종속적인 최적화를 수행한 후 백엔드에서 사용하는 RTL(Register Transfer Language)을 생성한다.
- 백 엔드 단계
- 백엔드에서는 아키텍쳐 종속적인 최적화를 수행한다.
- 아키텍쳐 종속적인 최적화란 아키텍쳐 특성에 따라 최적화를 수행하는 것을 말한다. 같은 기능을 수행하는 명령이여도 CPU 아키텍쳐 별로 더욱 효율적인 명령어로 대체하여 성능을 높이는 작업을 예로 들수 있다.
- 미들엔드에서 받은 RTL을 이용하여 아키텍쳐 종속적인 최적화를 수행하고 최적화가 완료되면 어셈블리 코드를 생성한다.
- 아키텍쳐 종속적인 최적화를 수행하면 아키텍쳐만 이해할 수 있는 언어가 되기 때문에 아키텍쳐가 맞지 않으면 어셈블리 코드를 해석할 수 없다.
- 프론트 엔드 단계
- 어셈블리
- 어셈블리어 정의
- 기계어는 다른말로 명령어라 부르는데 명령어는 0101010과 같은 이진수로 이루어진 숫자로 CPU 종류마다 고유한 내용을 가지고 있다.
- 어셈블리어는 이런 명령어를 사람이 이해 할 수 있게 부호화한 것으로 CPU 명령어와 1대1로 매칭된다.
- 어셈블리 과정
- 어셈블리 과정은 어셈블러를 통해 어셈블리어 파일(*.s)을 오브젝트 파일(*.o)로 변환하는 과정이다.
- 오브젝트 파일 정의
- 오브젝트 코드로 구성된 파일을 오브젝트 파일이라 부르며 이 오브젝트 파일은 특정한 파일 포맷을 가진다.
- 어셈블리어 정의
- 링킹 단계
- 링킹 단계에서는 컴파일러가 생성환 목적 파일들과 외부 라이브러리 파일들을 모아서 실행 파일(exe)을 생성한다.
- 이 링크 작업을 하는 프로그램을 링커라고 한다.
- 이 링킹 과정이 끝나게 되면 사용하는 시스템에 따라서 각기 다른 형태의 파일들을 생성하게 된다.