인터프리터와 컴파일러는 프로그램 소스를 인간이 이해할 수 있는 형태에서 컴퓨터가 실행할 수 있는 형태로 변환하기 위해 필요함
이들은 코드의 정확성을 검증하고, 최적화된 실행을 가능하게 하여 소프트웨어 개발과 실행 효율성을 높임
컴파일러와 인터프리터 언어의 등장 배경
컴퓨터 언어는 컴퓨터 하드웨어가 이해하고 실행할 수 있는 명령어를 작성하기 위해 등장했음
초기 컴퓨터는 기계어(machine language)로 프로그램을 작성했는데, 이는 매우 복잡하고 오류가 발생하기 쉬웠음
이를 개선하기 위해 어셈블리어(assembly language)가 등장했으며, 이는 기계어에 대한 인간의 이해를 돕기 위해 기호(symbols)와 명령어(opcodes)를 사용했음
1950년대에 들어서면서 고급 언어(high-level language)가 개발되기 시작했고, 컴파일러(compiler)를 사용하여 이러한 고급 언어를 기계어로 변환함으로써 프로그래밍의 효율성과 생산성이 크게 향상되었음
컴파일러는 소스 코드를 한 번에 모두 번역하여 실행 파일을 생성함으로써 실행 속도가 빠르다는 장점이 있음
한편, 1960년대에는 인터프리터(interpreter) 언어가 등장함
인터프리터 언어는 소스 코드를 한 줄씩 읽고 즉시 실행하는 방식으로 동작함
이러한 방식은 디버깅과 테스트 과정에서 유용하며, 프로그램의 즉각적인 피드백을 제공함
그러나 실행 속도 면에서는 컴파일러 언어에 비해 느릴 수 있음
결국, 컴파일러와 인터프리터 언어는 각기 다른 용도와 장점을 가지고 발전해왔으며, 현대에는 많은 언어들이 컴파일러와 인터프리터 방식을 혼합하여 사용하고 있음
예를 들어, Java는 바이트코드(bytecode)로 컴파일된 후, JVM(Java Virtual Machine)에서 인터프리터 방식으로 실행됨으로써 두 방식의 장점을 모두 활용함
특징 비교
특징 | 컴파일 언어 | 인터프리터 언어 |
실행 방식 | 전체 소스 코드를 한 번에 번역하여 실행 파일 생성 | 한 줄씩 번역하여 실행 |
실행 주체 | 운영체제와 CPU | 파이썬 가상 기계(Python Virtual Machine, PVM) |
속도 | 상대적으로 빠름 | 상대적으로 느림 |
에러 발견 | 모든 에러가 컴파일 과정에서 한 번에 발견됨 | 모든 에러를 한 번에 찾을 수는 없음 한 줄씩 실행시킬 수 있어 에러를 발견한 즉시 수정 및 테스트 가능 |
배포 | 기계어 실행 파일 배포 | 소스 코드 그대로 배포 |
예시 | C, C++, Go | 파이썬, 자바스크립트 |
동작 순서
컴파일러 (Go)
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
- 소스 코드 작성
- 어휘 분석
컴파일러는 소스 코드를 읽고, 이를 작은 단위(토큰)로 나눔
예를 들어, package, main, import, "fmt", func, main, (), {, fmt.Println, ("Hello, World!"), } 등이 토큰이 됨. - 구문 분석
토큰들을 문법적으로 분석하여 프로그램 구조를 이해함
예를 들어, func main() { ... }는 함수 정의라는 것을 이해함. - 중간 코드 생성
소스 코드를 중간 코드 형태로 변환함
이 중간 코드는 최적화를 위해 사용됨 - 컴파일 에러 처리
어휘 분석, 구문 분석, 중간 코드 생성 단계에서 발생하는 오류는 오류 메시지를 출력하고 컴파일을 중단함
예를 들어, 문법 오류, 타입 오류, 선언되지 않은 변수 사용 등이 있음
문법 오류 발생 시 "./main.go:3:2: undefined: fmt"와 같은 메시지를 출력함 - 최적화
중간 코드를 최적화하여 실행 속도를 높임
불필요한 부분을 제거하거나 더 효율적인 방식으로 변환함 - 기계어 생성
최적화된 중간 코드를 기계어로 변환함
기계어는 컴퓨터가 직접 이해하고 실행할 수 있는 코드임 - 실행 파일 생성
최종적으로 기계어로 변환된 코드를 실행 파일로 만듦 - 실행 파일 실행
운영 체제가 이 파일을 로드하고, CPU가 기계어 명령을 직접 실행함 - 런타임 에러 처리
프로그램 실행 중에 발생하는 오류는 운영 체제에 의해 감지되고 보고됨
예를 들어, 메모리 접근 오류, 논리적 오류 등이 있음
인터프리터 (Python)
print("Hello, World!")
- 소스 코드 작성
- 어휘 분석
파이썬 인터프리터는 소스 코드를 읽고, 이를 작은 단위(토큰)로 나눔
예를 들어, print와 "Hello, World!"가 토큰이 됨 - 구문 분석
토큰들을 문법적으로 분석하여 프로그램 구조를 이해함
예를 들어, print("Hello, World!")는 함수 호출이라는 것을 이해함 - 바이트코드 생성
구문 분석이 끝난 후, 소스 코드를 바이트코드라는 중간 형태로 변환함
바이트코드는 소스 코드보다 간단하고 최적화된 형태임
이 단계에서 파이썬 인터프리터는 .pyc 파일을 생성할 수 있음 - 바이트코드 실행
파이썬 가상 기계(Python Virtual Machine, PVM)는 바이트코드를 읽고, 이를 실행함
print("Hello, World!") 줄을 바이트코드로 변환하면, PVM은 이 명령어를 해석하고 화면에 "Hello, World!"를 출력함 - 에러 처리
실행 도중 에러가 발생하면 인터프리터는 그 즉시 에러를 보고하고, 해당 줄 이후의 코드는 실행하지 않음
인터프리터 언어에서 가상 기계를 사용하는 이유
- 이식성
- 가상 기계는 특정 하드웨어나 운영 체제에 종속되지 않아 바이트코드가 여러 플랫폼에서 동일하게 실행될 수 있음
- 참고 : 컴파일언어는 특정 하드웨어나 운영체제에 종속됨
하드웨어에 종속되는 예시 → x86 아키텍처용으로 컴파일된 C 프로그램은 ARM 아키텍처의 컴퓨터에서 실행되지 않음
운영체제에 종속되는 예시 → 리눅스용으로 컴파일된 C 프로그램은 윈도우 운영 체제에서 실행되지 않음
- 성능 향상
- 바이트코드는 소스 코드보다 더 간단하고 최적화된 형태임
- 가상 기계는 바이트코드를 실행하기 위해 설계된 최적화된 환경을 제공함
- Just-In-Time (JIT) 컴파일러와 같은 기술을 사용하여 바이트코드를 실행 중에 네이티브 기계어로 변환하고, 이를 캐싱하여 반복 실행 시 성능을 향상시킴
- 보안 및 안정성
- 가상 기계는 프로그램의 실행을 제어하고 격리된 환경에서 실행할 수 있어 보안성을 높임
- 가상 기계는 메모리 관리, 예외 처리, 접근 제어 등의 기능을 제공하여 안정성을 보장함
- 동적 기능 지원
- 가상 기계는 동적 타이핑, 동적 코드 실행 등의 기능을 지원할 수 있음
- 이는 인터프리터 언어의 유연성을 극대화하는 데 도움이 됨
인터프리터 언어가 컴파일 언어보다 실행속도가 느린 이유
- 실시간 해석
- 해석 비용
인터프리터는 프로그램의 각 명령어를 실시간으로 해석함
소스 코드를 읽고, 해석하고, 실행하는 과정을 반복하기 때문에 추가적인 시간이 소요됨
반면, 컴파일러는 이 과정을 미리 수행하여 기계어로 번역된 실행 파일을 생성함 - 반복 작업
인터프리터는 반복적으로 코드를 해석해야 함
예를 들어, 루프 안에 있는 코드를 여러 번 실행할 때마다 같은 코드를 반복적으로 해석하고 실행해야 함.
- 해석 비용
- 최적화 부족
- 최적화 수준
컴파일러는 중간 코드 생성 및 최적화 단계를 통해 프로그램을 더 효율적으로 실행할 수 있도록 최적화함
반면, 인터프리터는 즉시 실행을 목표로 하기 때문에 최적화 과정이 상대적으로 부족함
- 최적화 수준
- 바이트코드 및 가상 기계
- 추가 계층
일부 인터프리터는 소스 코드를 바이트코드로 변환한 후 이를 가상 기계에서 실행함
이 경우, 바이트코드를 해석하고 실행하는 추가 계층이 필요하기 때문에 실행 속도가 느려짐
예를 들어, 파이썬은 소스 코드를 바이트코드로 변환한 후 파이썬 가상 기계(PVM)에서 이를 실행함
- 추가 계층
- 동적 타입 검사
- 타입 검사
인터프리터는 프로그램 실행 중에 변수의 타입을 검사해야 함
이 동적 타입 검사는 추가적인 계산 작업을 요구하며, 실행 속도를 저하시키는 요인 중 하나임
예를 들어, 파이썬과 같은 동적 타이핑 언어에서는 변수의 타입을 실행 중에 결정하고 검사함
- 타입 검사
'Computer Science' 카테고리의 다른 글
지역 심벌과 전역 심벌 (0) | 2024.07.24 |
---|---|
링커란? (0) | 2024.07.17 |
Brute force(브루트 포스)란? (0) | 2024.07.09 |
스택(stack)과 힙(Heap) (0) | 2024.03.27 |
명령형 프로그래밍 vs 선언형 프로그래밍 (0) | 2024.03.02 |