2024. 12. 19. 15:38ㆍ컴파일러
컴파일러는 Source code을 컴파일 과정을 통해 목적 program으로 바꿔주는 과정을 말한다.
여기서 source code이란 C/C++, JAVA와 같은 언어들을 말하는데 이것들은 우리가 이용하는 자연어와 비슷하다.
이 자연어를 기계어로 변형하고 이 기계어를 해당 기계에 돌아가도록 program으로 만들어주는 것이다.
그렇지만 기계들은 매우 다양한 종류가 있으며 기계어는 2진수로 이루어져 있고 이것은 하드웨어에 종속적이다.
그러므로 어떤 프로세서를 따르냐에 따라 다르게 해석되게 된다.
ex) MIPS, RISC-V, AMD 등
위의 해석 내용은 컴퓨터구조를 따로 공부해서 봐야한다.
컴파일러와 비슷한 개념으로 interpreter가 있는데 interpreter와 컴파일러의 차이는 source program이 목적 프로그램까지 가능 과정이 다르다.
전체적인 차이
특성 | 컴파일러 | 인터프리터 |
실행 방식 | 소스 코드 전체 변환 후 실행 | 소스 코드 한 줄씩 실행 |
결과 | 실행 가능한 바이너리 파일 생성 | 결과물 없이 직접 실행 |
속도 | 한 번 변환 후 실행하여 빠름 | 한 줄씩 실행하므로 느림 |
오류 | 컴파일 시 한 번에 모든 오류를 탐지 | 실행 중 발생한 시점의 오류만 탐지 |
사용 예시 | C/C++, Rust | python, javascript |
이 두 방법을 같이 이용한 JAVA의 JAVA Virtual Machine은 JAVA 컴파일러에서 변환한 바이트 코드를 이용해서 이것을 해석하는 JVM을 통해 실행하게 된다.
그렇기 때문에 JVM만 있다면 하드웨어 종속적인 바이너리 파일과 달리 어디에서나 실행할 수 있게 된다.
컴파일러만을 따로 떼서 전체적인 방법을 본다.
컴파일러는 위의 단계를 거치고 있고 여기서 lexical analyzer, syntax analyzer, semantic analyzer, intermediate code generator 이 4 단계가 하드웨어 독립적인 단계들이다.
lexical analysis는 soure code의 한줄씩 읽어오면서 문장의 가장 최소 단위로 자른 뒤에 lexemes와 token으로 나누게 된다. lexemes는 문장을 잘라낸 것들의 실제 내용들이고, token은 이 lexemes대한 분류들이다.
그리고 이것들을 갖고 symbol table을 만들어서 관리한다.
이렇게 나온 결과들을 갖고 token streams 형태로 syntax analysis에 전달하게 된다.
syntax analysis는 문법이 맞는지 parse tree, syntax tree를 만들어내어 확인하게 된다.
token들이 들어오게 되면 Finite automata로 표현하고 이것들을 확인하면서 문법을 체크한다.
정확하게는 context free grammar를 갖고 이 grammar가 갖을 수 있는 모호성(ambiguity)를 처리한 뒤에 의미 분석(semantic analysis)를 하게 된다. 이것들을 LR parsing, LL parsing과 같은 방법으로 각 token들을 읽어 나가게 된다.
semantic analysis에서는 grammar를 갖고 의미를 해석하는 단계로 정해진 semantic rule에 맞게 해석하는 것이다.
이 과정에서 type과 attribute를 부여하게 되는데 type은 우리가 알고 있는 int, string과 같은 것들이고, attribute는 Inherited, synthesized 와 같은 값을 어떻게 계산하고 전달할 것인지에 대해 나타낸다.
Synthesized는 부모 노드는 자식에서 값을 받아서 계산된 속성이다.
Inherited는 노드의 부모나 형제 node에서 전달 받은 속성이다.
이렇게 체크했다면 intermediate representation(IR)을 생성한다.
IR
- 플랫폼 독립성:
- 소스 코드가 특정 하드웨어나 운영 체제에 종속되지 않도록 함.
- 동일한 IR을 사용하여 여러 플랫폼에 대해 코드 생성 가능.
- 코드 최적화:
- IR은 간단하고 구조화된 형태로 변환되므로, 최적화 알고리즘을 쉽게 적용 가능.
- 예: 루프 전개, 상수 접합, 죽은 코드 제거 등.
- 다중 언어 지원:
- 여러 프로그래밍 언어의 코드를 동일한 IR로 변환하여 하나의 백엔드에서 처리 가능.
- 예: LLVM은 다양한 언어(C, C++, Rust 등)의 IR을 지원.
- 다중 타겟 지원:
- 하나의 IR에서 다양한 하드웨어에 맞는 기계어를 생성 가능.
- 예: x86, ARM, RISC-V 등에 대해 코드 생성.
IR은 저수준에 가까울수록 기계어와 유사한 구조로 만드는 것이고, 고수준일수록 소스코드와 비슷한 구조로 만드는 것이다. 그리고 하드웨어 종속적으로 기계어로 변하게 된다.