[기술 질문] #8_컴파일러, Compiler
컴파일러에 대해 알아보겠습니다.
Overview
- 개념
- 컴파일 과정
- 컴파일러 vs 인터프리터
- C# vs C++
- 게임 프로그래밍의 C++ 사용 이유
#1. 개념
1. 컴파일러??
- 컴파일(Compile)은 주어진 언어로 작성된 컴퓨터 프로그램을 다른 언어의 동등한 프로그램으로 변환하는 프로세스입니다.
- 일반적으로, 컴파일러는 고급 언어로 작성된 코드들을 컴퓨터 언어로 변환하는 작업을 일컫습니다.
2. 인터프리터??
- 인터프리터는 고급 언어로 작성된 원시 코드를 바로 실행하는 프로그램 또는 환경을 의미합니다.
- 일반적으로, 인터프리터는 고급언어로 작성된 원시 코드 명령어들을 한 번에 한 줄씩 읽어가며 실행하는 프로그램입니다.
- 소스 코드를 직접 실행하거나, 소스 코드를 다른 효율적인 중간 코드로 변환하고, 변환한 것을 바로 실행합니다. 더불어, 인터프리터 시스템의 일부인 컴파일러가 만든 미리 컴파일된 저장 코드의 실행을 호출하기도 합니다.
#2. 컴파일 과정
1. 전처리
- 전처리기를 통해 소스 코드 파일(*.c)을 전처리된 소스 코드 파일(*. i)로 변환하는 과정입니다. 이 과정에서 전처리기는 세 가지 작업을 수행합니다-주석 제거, 헤더 파일 삽입, 메크로 치환 및 적용.
- 주석 제거는 말 그대로 소스 코드에 작성된 주석들을 모두 제거합니다.
- 헤더 파일 삽입 작업은 "#include" 지시문을 만나 해당 헤더파일을 찾아 모든 내용을 복사해서 소스코드에 삽입하는 작업입니다. 헤더 파일에서 선언된 함수 원형은 후에 링킹 과정을 통해 실제 함수가 정의되어 있는 오브젝트 파일과 결합합니다.
- 메크로 치환 및 적용 작업은 "#define" 지시문에서 정의한 메크로를 저장하고, 치환하는 작업을 수행합니다.
2. 컴파일
- 전처리된 소스 코드 파일(*.i)를 어셈블리어 파일(*. s)로 변환합니다. 일반적으로, 언어의 문법 검사와 함께 Data영역의 메모리 할당을 수행합니다.
- 컴파일러는 총 세 단계로 구성되어 있습니다-프런트 엔드, 미들 엔드, 백 엔드.
- 프론트 엔드는 언어 종속적인 부분을 처리합니다. 소스 코드가 해당 언어의 문법에 맞게 작성되었는지 확인하고, 어휘 분석(lexical analysis), 구문 분석(syntax analysis), 의미 분석(semantic analysis) 등의 과정을 거쳐 언어 종속적인 문제를 해결합니다. 이 과정을 통해 중간 표현(Intermediate Representation, IR)인 일반적으로 GIMPLE 또는 SSA(Static Single Assignment) 트리 등의 형태로 소스 코드를 변환합니다.
- 미들 엔드는 아키텍처 비종속적인 최적화를 수행합니다. 아키텍처 비종속적인 최적화란, CPU 아키텍처에 상관없이 적용할 수 있는 최적화입니다. 미들 엔드는 프런트 엔드에서 생성된 중간 표현을 바탕으로 다양한 최적화 기법을 적용하여 프로그램의 성능을 향상시킵니다.
- 백 엔드는 아키텍처 종속적인 최적화를 수행하고, 최종 어셈블리 코드를 생성합니다. 아키텍처 종속적인 최적화 작업은 각 아키텍처의 특성에 맞는 최적화를 수행하는 과정입니다. 이를 통해 CPU 아키텍처에 특화된 명령어를 생성하는 등의 작업을 수행합니다. 백 엔드에서는 미들 엔드에서 처리된 중간 표현을 바탕 어셈블리 코드로 변환하고, 이를 바이너리 코드로 컴파일하여 실행 파일을 생성합니다.
3. 어셈블리
- 어셈블러를 통해 어셈블리어 파일(*. s)을 오브젝트 파일(*. obj)로 변환합니다.
4. 링킹
- 링커는 오브젝트 파일들을 묶어 실행 파일로 만드는 작업을 수행합니다.
- 정확히 말해서, 링커는 이 과정에서 오브젝트 파일들과 프로그램에서 사용하는 라이브러리 파일들을 링크하여 하나의 실행 파일을 만듭니다.
#3. 컴파일러 vs 인터프리터
1. 컴파일러 특징
- 컴파일러는 원시 코드로 작성된 프로그램 전체를 목적(오브젝트) 프로그램으로 번역한 후, 링킹 작업을 통해 컴퓨터에서 실행 가능한 프로그램을 생성합니다.
- 여러 과정들을 거쳐야 하기 때문에, 비교적 많은 시간이 소모되지만, 한번 변역 된 프로그램은 다음 사용 시 다시 번역할 필요가 없습니다.
2. 인터프리터 특징
- 인터프리터는 원시 코드를 한 번에 한 줄씩 번역하고, 변역과 동시에 프로그램을 한 줄 단위로 그 즉시 실행시키는 프로그램입니다.
- 인터프리터는 변역 속도가 빠르지만, 매 실행마다 번역 작업을 수행합니다.
3. 컴파일러와 인터프리터의 차이점
구분 | 컴파일러 | 인터프리터 |
번역 단위 | 전체 | 줄 |
목적 프로그램 | 생성 | 비 생성 |
실행 속도 | 첫 실행시 느림, 이후에 빠름 | 빠르지만, 매 실행 마다 번역 수행 |
번역 성능 | 느림 | 빠름 |
언어 | C, JAVA | Python, BASIC, LISP, ...etc |
#4. C++ vs C#
1. C++과 C#의 차이점
구분 | C++ | C# |
컴파일 결과 | 어셈블리어(저수준) | 중간 수준 언어 |
값 타입 | 정적 할당한 모든 타입 | 기본 타입, 구조체 |
참조 타입 | 동적 할당한 모든 타입 | 클래스, 객체 |
동적 할당 | 포인터 변수 | 참조 타입(클래스 객체) |
메모리 해제 | 수동 | 자동(가비지 컬렉션) |
2. 컴파일 결과
- C++로 작성한 코드는 컴파일 과정을 통해 어셈블리어로 작성된 복적 파일이 생성됩니다.
- C#으로 작성한 코드는 운영체제 위에. NET이라는 가상 머신 실행 환경에서 실행되기 때문에, , NET에 적합한 IL(Intermediate Language)로 컴파일됩니다.
- C#의 경우. NET이 없는 시스템에선 애플리케이션 실행이 불가능하겠죠.
- C++의 경우 컴파일 시간이 C#에 비해 비교적 느립니다. 고수준 언어 -> 저수준 언어로 변환하는 속도보다 고수준 언어-> 중간 수준 언어로 변환하는 속도가 빠르기 때문이죠.
3. 값 타입(일반 변수)
- C++의 값 타입은 정적으로 할당한 모든 형태의 데이터입니다.
- C#의 값 타입은 기본 타입과 구조체만을 의미합니다.
4. 참조 타입
- C++의 참조 값은 동적으로 할당한 모든 형태의 데이터와 &기호를 붙여 선언한 참조형 변수입니다.
- C#의 참조 값은 클래스로 만든 객체를 참조 타입으로 취급합니다.
5. 포인터 및 동적 할당
- C++의 모든 데이터 타입은 포인터 변수로 선언할 수 있습니다. 더불어, 동적 할당 또한 가능합니다. 하지만, 메모리 관리를 직접 해야 합니다.
- C#은 포인터 개념이 존재하지만, 잘 사용하지 않는 것으로 알고 있습니다. C#은 가비지 컬렉션이 자동으로 메모리를 관리해 주기 때문에, 불안전한 포인터 사용을 꺼려하는 것 같습니다.
#5. 게임 프로그래밍과 C++
1. C++ 사용 이유
- 관성적으로, 개발 환경과 미들웨어(Unreal) 등이 모두 C++로 개발되어 있기 때문입니다.
- C++로 작성된 코드는 컴파일 과정에서 어셈블리어(저수준 언어)로 변환되어, 하드웨어 수준의 구성 요소와 메모리에 보다 직접적인 제어가 가능해집니다. 결과적으로, 하드웨어와 그래픽 프로세스를 직접 제어하는 것이 가능해집니다.
- 개발자가 직접 메모리 할당과 관리를 수행함으로써, 방대한 양의 데이터 리소스를 최적화하기 용이합니다.