컴파일러: 두 판 사이의 차이

잔글 (문자열 찾아 바꾸기 - "[[분류: " 문자열을 "[[분류:" 문자열로)
잔글 (문자열 찾아 바꾸기 - "이 때는" 문자열을 "이때는" 문자열로)
3번째 줄: 3번째 줄:
하지만 항상 그런 것은 아니며, [https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Emscripten Emscripten]처럼 [[C(프로그래밍 언어)|C]]를 [[JavaScript]]로 바꾸는 컴파일러 같은 것도 존재한다. [[유닉스]]나 [[리눅스]]와 같이, [[운영체제]]에 따라서는 운영체제를 구성하는 한 부분으로 분류하는 경우도 있다.
하지만 항상 그런 것은 아니며, [https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Emscripten Emscripten]처럼 [[C(프로그래밍 언어)|C]]를 [[JavaScript]]로 바꾸는 컴파일러 같은 것도 존재한다. [[유닉스]]나 [[리눅스]]와 같이, [[운영체제]]에 따라서는 운영체제를 구성하는 한 부분으로 분류하는 경우도 있다.


기계어 컴파일의 경우, 컴파일된 결과물은 별다른 중간 해석 절차 없이 하드웨어 위에서 그대로 실행시킬 수 있다. 때문에 성능을 중요시하는 언어들은 많은 경우 기계어로 컴파일할 것을 염두에 두고 디자인된 경우가 많다. 대표적으로 [[C_(프로그래밍_언어)|C 언어]], [[C++]]가 이에 속한다. 반면에, 스크립팅의 경우 한두번 쓰고 버려지는 코드를 주로 짜거나, 혹은 그때그때 코드를 고치면서 즉시 결과를 확인하는 식으로 작업하는 경우가 많은데, 이 때는 실행 속도가 아무리 빨라 봤자 중간에 컴파일을 거쳐야 한다는 번거로움과 기나긴 컴파일 속도가 더 방해가 되곤 한다. 때문에 많은 [[스크립트 언어]]들은 컴파일러 대신 [[인터프리터]]로 구현된다.
기계어 컴파일의 경우, 컴파일된 결과물은 별다른 중간 해석 절차 없이 하드웨어 위에서 그대로 실행시킬 수 있다. 때문에 성능을 중요시하는 언어들은 많은 경우 기계어로 컴파일할 것을 염두에 두고 디자인된 경우가 많다. 대표적으로 [[C_(프로그래밍_언어)|C 언어]], [[C++]]가 이에 속한다. 반면에, 스크립팅의 경우 한두번 쓰고 버려지는 코드를 주로 짜거나, 혹은 그때그때 코드를 고치면서 즉시 결과를 확인하는 식으로 작업하는 경우가 많은데, 이때는 실행 속도가 아무리 빨라 봤자 중간에 컴파일을 거쳐야 한다는 번거로움과 기나긴 컴파일 속도가 더 방해가 되곤 한다. 때문에 많은 [[스크립트 언어]]들은 컴파일러 대신 [[인터프리터]]로 구현된다.


== 구조 ==
== 구조 ==

2015년 5월 4일 (월) 09:50 판

프로그래밍 언어로 쓰인 코드를 다른 프로그래밍 언어의 코드로 번역하는 컴퓨터 프로그램. 이론적으로는 컴파일러가 존재하여 실행파일로 만들 수 있는 모든 프로그래밍 언어로 작성될 수 있고, 모든 종류의 원시 코드를 다른 언어의 목적코드로 번역할 수 있다. 하지만 보통은 좀 더 고수준의 언어를 저수준의 언어로 변환하는 프로그램을 주로 말한다. [1] 하지만 항상 그런 것은 아니며, Emscripten처럼 CJavaScript로 바꾸는 컴파일러 같은 것도 존재한다. 유닉스리눅스와 같이, 운영체제에 따라서는 운영체제를 구성하는 한 부분으로 분류하는 경우도 있다.

기계어 컴파일의 경우, 컴파일된 결과물은 별다른 중간 해석 절차 없이 하드웨어 위에서 그대로 실행시킬 수 있다. 때문에 성능을 중요시하는 언어들은 많은 경우 기계어로 컴파일할 것을 염두에 두고 디자인된 경우가 많다. 대표적으로 C 언어, C++가 이에 속한다. 반면에, 스크립팅의 경우 한두번 쓰고 버려지는 코드를 주로 짜거나, 혹은 그때그때 코드를 고치면서 즉시 결과를 확인하는 식으로 작업하는 경우가 많은데, 이때는 실행 속도가 아무리 빨라 봤자 중간에 컴파일을 거쳐야 한다는 번거로움과 기나긴 컴파일 속도가 더 방해가 되곤 한다. 때문에 많은 스크립트 언어들은 컴파일러 대신 인터프리터로 구현된다.

구조

컴파일러는 크게 5 가지의 요소로 구성되어 있다고 할 수 있다. 각각의 기능은 컴파일링에 필요한 자료구조를 공유하며 동작한다. 이 구성요소들을 크게 두 부분(Front-end, Back-end)로 구분할 수 있다. Front-end 부분(스캐너, 파서, 의미해석기)는 원시코드에 의존적이고 Back-end 부분은 머신에 의존적이다. 따라서 둘은 독립적으로 동작하고, 다른 환경으로의 이식이 필요할 경우 해당 부분만을 고치면 사용 가능하다.

스캐너(Scanner)

단순한 문자열의 나열에 불과한 원시코드를 읽어들여 의미를 갖는 최소한의 단위인 토큰으로 나누어 반환하는 부분이다. 프로그래밍 언어적인 의미를 갖는다는 점에서 단순한 어휘소인 Lexeme 과는 구별된다. 예를 들어 int a = 3;이라는 문장이 있을 때, 각각의 "int", "a", "=", "3", ";" 는 단순한 Lexeme이지만 각각의 어휘들이 '예약어', '식별자', '특수기호 = ', '상수', '특수기호 ;' 라는 의미를 부여받는다면 이는 토큰이다. 따라서 스캐너를 토크나이저(Tokenizer)라고 부르기도 한다.

스캐너는 정규표현식(Regular Expression)을 기반으로 하는 DFA(Deterministic Finite Automata)를 구현하여 제작된다. 스캐너가 판별할 수 있는 것은 현재의 입력 문자가 프로그래밍 언어에 포함되는 문자가 맞는지, 2글자 이상으로 이루어진 문자열을 갖는 토큰일 경우 올바른 순서대로 해당 문자가 입력되었는지 (예를 들어 =! 와 같은 연산자는 올바르지 않다)만을 판별하므로 문법구문의 오류나 의미상의 오류를 파악하는 것은 불가능하다.

파서

스캐너로 얻은 토큰을 이용해서 파스 트리를 생성하는 부분이다. 이를 통해서 프로그램의 소스 코드가 문법(syntax)에 일치하는지 파악할 수 있기 때문에 신택스 애널라이저(Syntax Analyzer)라고도 부른다. 이렇게 만든 파스 트리는 코드를 왼쪽에서 오른쪽/오른쪽에서 왼쪽으로 해석하는지, 토큰의 string 유도가 왼쪽에서 오른쪽/오른쪽에서 왼쪽으로 일어나는지, look-ahead(현재 토큰의 다음에 올 토큰)을 최대 몇 개나 고려하여 설계되는지, 어떻게 실행되는지 등의 기준에 따라서 몇 가지로 분류 가능하다.


  • 재귀 하강 파서(Recursive-descendant Parser)

이름대로 파스트리를 Top-down 방식으로 재귀적 호출을 해가며 순회하는 파서. 손으로도 쉽게 코딩할 수 있을만큼 간단하지만 Error chaining(실제 오류는 n번 일어났지만 상위 단계의 문법에서도 오류가 있다고 처리되어 한 오류에 대하여 여러번의 오류 알림이 일어나는 현상.) 현상을 처리하기 힘들다는 단점이 존재한다.

  • LL(1) 파서

Left-right, Leftmost derivation, Maximum look-ahead 1개를 의미하며 재귀호출을 이용하지 않고 명시적인 stack을 이용한다.

  • LR(1) 파서
  • SLR(1) 파서
  • LALR(1) 파서

의미 해석기

옵티마이저

목적코드 생성기

더 보기

참조

  1. 여기에서 고수준과 저수준을 나누는 기준은 단순히 사용자와 컴퓨터 중 어느 쪽에 더 친숙한지, 즉 자연어와 기계어 중 어느 쪽에 더 가까운지를 의미하며, 고급 언어라고 꼭 제공되는 기능이 더 많으란 법도 없고, 저급 언어라고 복잡한 기능이 없으리란 법도 없다. 오히려 저급언어일수록 컴퓨터 하드웨어 컴포넌트의 제어를 직접 할 수 있다. 대신 익히는 데 걸리는 노력과 시간, 그리고 생산성 측면에서는 많은 경우 고급 언어가 유리하다.