어셈블리어
컴퓨터는 기호로써 0과 1을 사용한다. 그렇기 때문에 프로그래머들은 0과 1을 조합해서 컴퓨터에게 명령을 내려야했다.
하지만 사람이 0과 1로 이루어진 비트 조합을 외워서 프로그램을 짜기란 쉽지 않았다. 그래서 비트 조합을 대신할 수 있는 이름을 붙이기 시작했다. 예를 들어 메모리에서 데이터를 가져오는 001에는 LOAD, 메모리에 데이터를 저장하는 010에는 STORE라는 이름을 붙여주는 식으로 말이다.
이것이 바로 어셈블리어다. 어셈블리어는 기계어와 달리 주소에 이름도 붙일 수 있었고, 코드에 주석을 달 수도 있었다.
고수준 언어
어셈블리어는 기계어보다는 편리했지만, 여전히 간단한 일을 하기 위해서도 여러 줄의 코드가 필요했다.
#include<stdio.h>
void main() {
int a = 10, b = 20, c;
asm {
mov ax,a
mov bx,b
add ax,bx
mov c,ax
}
printf("c= %d",c);
}
코드 한 줄로 더 많은 작업을 처리할 수 있도록, 어셈블리어보다 더 높은 추상화를 이뤄낸 고수준 언어가 등장했다. 고수준 언어는 크게 컴파일러라는 프로그램에 의해 실행되는 컴파일러 언어와 인터프리터에 의해 실행되는 인터프리터 언어로 나뉜다.
<참고>
한 언어가 컴파일러 언어의 성격과 인터프리터 언어의 성격을 모두 가지고 있는 경우도 있기에 특정 언어를 컴파일러 언어 또는 인터프리터 언어로 분류하기는 것은 대체로 적절하지 못하다. 다만 컴파일러와 인터프리터의 차이를 설명하기 위해 구분해서 이야기하고자 한다.
컴파일러는 고수준의 언어를 기계어로 번역(컴파일)해서 실행시킨다. 반면 인터프리터 언어는 가상 머신에서 실행된다. 번역이 끝난 기계어를 실행하는 컴파일러 언어와 달리 인터프리터 언어는 코드가 즉시 번역되어 실행된다.
하지만 컴파일러든, 인터프리터든 프로그래머가 작성한 코드를 사전에 처리할 필요가 있다. '1+2'과 '1 + 2'는 겉보기에는 다르지만 같은 결과를 제공해야하기 때문이다.
파스트리
먼저 어휘분석이 이루어져야한다. 코드를 토큰(단어)로 바꾸는 과정이다. '컴퓨터 구조'라는 말을 컴퓨터/구조로 나누어 생각하도록 만드는 것과 동일하다.
그런데 코드에서 사용하는 토큰에는 종류가 다양하다. 1+2에서 '1,2'는 숫자이지만 '+'는 연산자다. 이런 토큰의 유형을 구분하기 위해서는 정규식을 사용할 수 있다.
[0-9]+ 라는 정규식은 0~9까지의 숫자 중 한 개의 문자가 한 개 이상 등장한다는 뜻이다. 즉, 이 정규식에 해당한다면 해당 토큰을 숫자(INTEGER)로 구분할 수 있다.
어휘 분석 이후에는 문법 파싱이 이루어진다. '1 + 2'와 '1 + + 2'는 숫자와 연산자라는 구성은 비슷하지만 후자는 계산할 수 없다. 이런 토큰들이 모인 시퀀스를 문법에 맞는 문장으로 분석하는 것이 문법 파싱이다.
이렇게 어휘분석과 문법 파싱이 마무리되면, 이제 프로그래머가 작성한 코드를 파스트리로 만들어낼 수 있다.
깊이 우선 탐색을 통해 파스트리는 손쉽게 실행될 수 있다. 인터프리터는 파스 트리를 탐색하며 계산을 수행하지만, 컴파일러는 어셈블리 코드를 만들어 낸다는 점이 다르다. 그리고 이 어셈블리어는 다시 기계어로 번역된다.
주의 사항
대부분의 언어는 파스트리를 분석해 더 효율적인 코드를 생성해내는 최적화라는 단계를 포함하고 있다.
a = 10;
b = 20;
for (int i = 0; i < 10; i++) {
x = a + b;
result[i] = i * x;
}
위 코드를 보면 a + b는 루트를 반복해도 변경되지 않는 루프 불변 요소이다. 최적화기는 최적화를 위해 x = a + b를 루프 밖으로 꺼내서 단 한 번만 계산하게 변경한다. 또는 곱셈과 같은 비용이 많이 드는 연산을 덧셈과 같은 비용이 더 적게 드는 연산으로 변경하기도 한다.
그런데 하드웨어를 조작하는 코드가 최적화 되면 문제가 발생할 수도 있다.
light = on();
불을 키기 위해 위와 같은 코드가 있다고 가정해보자. light라는 변수는 쓰기에만 사용되고 읽지는 않으므로 최적화기는 이 코드를 불필요하다고 판단해 코드를 제거할 수 있다. 따라서 일부 언어에서는 최적화에서 제외시키는 메커니즘을 제공하기도 한다.
---- 확인 문제 ----
1. 고수준 언어는 (A) -> (B) -> (C)구성을 거친 후 인터프리터 혹은 컴파일러에 의해 실행/번역된다. (A)를 위해서는 정규식을 활용할 필요가 있다.
2. 컴파일러와 인터프리터의 차이점에 대하여 서술하시오.
---- 정답 ----
1. (A) : 어휘 분석
(B) : 문법 파싱
(C) : 파스 트리
2. 컴파일러는 프로그래머가 작성한 전체 코드(파스트리)를 기계어로 번역하는 역할을 맡는다. 인터프리터는 일종의 가상머신으로, 파스트리를 즉시 실행한다.
'CS > BOOK' 카테고리의 다른 글
[한 권으로 읽는 컴퓨터 구조와 프로그래밍] 10. 웹 브라우저를 활용한 프로그래밍 (2) | 2024.04.23 |
---|---|
[한 권으로 읽는 컴퓨터 구조와 프로그래밍] 9. 웹브라우저 (0) | 2024.04.17 |
[한 권으로 읽는 컴퓨터 구조와 프로그래밍] 5. 컴퓨터 아키텍처 (1) | 2024.03.21 |
[한 권으로 읽는 컴퓨터 구조와 프로그래밍] 4. 하드웨어의 구조 - CPU (0) | 2024.03.20 |
[한 권으로 읽는 컴퓨터 구조와 프로그래밍] 3-2. 컴퓨터의 데이터 저장 방법 (1) | 2024.03.19 |