- 프로그래밍 언어를 알기이전 사전에 읽어보아야할 내용들.


일의 순서(프로그램)을 알려줄 땐, 우리가 언어를 사용해서 의사소통 하듯이 컴퓨터에게도 일을 시키려고 할 때에도 이러한 의사소통 수단이 필요하다. 하지만 컴퓨터는 사실 0과 1의 조합된 기계어를 사용한다. 이 기계어를 통해서 우리는 컴퓨터에게 어떤 명령을 내리고 일을 처리할 수 있는 것이다.


하지만 컴퓨터가 왜 0과 1을 사용하는 것인가에 대해서 궁금함이 생긴다.


사실 이것을 이해하기 위해서는 CPU에 대해서 알아야하며, CPU 는 중앙처리장치로 흔히들 말하는 사람의 '뇌'에 해당한다. CPU는 단순 전기신호를 다른 회로와 주고 받는 일을 할 줄 안다. 더구나 전기신호는 'ON' 과 'OFF' 2개 밖에 없기 때문에 CPU는 이것만을 이해한다.

ON 과 OFF 의 개념을 사람이 이해하기 편하도록 0과 1에 대응시키게 되었으며 이로 인해 CPU가 보내는 전기신호를 2진수로 바꿀 수 있게 되었다. 그리고 2진수의 발견으로 전기신호로 만드는 것 또한 가능하게 되었으며 이후 2진수에서 10진수로의 변화가 찾아내었고, 이 변화는 우리가 일반적으로 사용할 수 있는 계산기를 만드는 것에 도달할 수 있게 되었다. 


그리고 문자 하나하나에 번호를 붙여서 문자를 숫자에 대응시키므로 문자도 전기신호로 교환할 수 있게 되었고 CPU에 문장을 처리시키는 것 또한 가능하게 되었다. 이후 그림이나 음악 등을 전기신호에 대응시킬 수 있게 되었으며 CPU에 전기신호로 명령을 보내는 방법 또한 발견하게 되면서 CPU의 이용범위가 넓어졌다.

따라서 위의 내용들을 살펴보면 다음과 같이 추론이 가능하다. 

(1) 컴퓨터의 뇌는 CPU이다.

(2) CPU는 ON/OFF 밖에 모른다. 하지만 이를 숫자 0과 1에 대응시킬 수 있다.

(3) 대응시킨 숫자는 2진수로 변환이 가능하고 2진수는 10진수로의 변환이 가능하다.
(4) 2진수로 컴퓨터와 대화가 가능해지며 문자, 그림, 소리 등을 전기신호로 변환할 수 있게 되었으며 더불어 인간이 내린 명령까지도 전기신호로 보내는 방법도 발견하게 되었다.



- 컴파일러, 인터프리터, JIT 는 그럼 또 무엇인가?

컴파일러(Compiler)는 특정 프로그래밍 언어로 쓰여있는 문서를 다른 프로그래밍 언어로 옮기는 프로그램을 말한다. 운영체제에서는 이를 언어 번역 프로그램이라고 부른다. 원래의 문서를 소스코드 혹은 원시코드라고 부르며, 컴파일러에 의해 변환된 코드를 목적코드라고 부른다. 목적 코드는 주로 다른 프로그램이나 하드웨어가 처리하기 용이한 형태로 출력된다.

하지만 목적코드를 사람이 볼 수 있는 문서파일이나 그림파일 등으로 옮기는 경우도 있다. 그리고 우리는 소스코드(=원시코드)에서 목적코드로 옮기는 과정을 컴파일(Compile)이라고 말한다. 컴파일러는 소스 프로그램을 읽어서 즉시 결과를 출력하는 인터프리터와 구분된다. 


하지만 많은 인터프리터들이 JIT(Just In Time) 컴파일 기술로 실시간 컴파일을 수행하므로 컴파일러와 인터프리터 사이의 기술적 구분은 사라져가는 추세이다. 사실 소스코드를 컴파일하는 근본적인 이유는 해당 소스코드가 사람이 이해하기 쉬운 고수준의 언어이지만 컴퓨터에게는 이해하기 어렵다. 따라서 해당 소스코드를 컴파일하여 컴퓨터가 이해하기 쉬운 저수준 언어, 즉 기계어 프로그램을 만들고자 함이 컴파일러 존재의 이유이다.



- 어셈블리어?
좁은 의미의 컴파일러는 주로 고수준으로 쓰인 소스코드를 저수준언어(어셈블리어 혹은 기계어)로 번역하는 프로그램을 의미한다. 여기서 어셈블리어가 기계어랑 동급으로 취급되는 것이 아닌 어셈블리어와 기계어는 일대일 대응이 되는 저수준의 언어라는 것이다.


그리고 간단한 문장에 대해서는 어셈블리어와 기계어 사이에 일대일 대응 관계가 있지만, 자주 쓰이는 몇 명령들은 둘 이상의 기계어 명령을 묶어서 하나의 어셈블리 명령어로 대응시키기도 한다.


추가로 어셈블러가 있는데 어셈블러는 어셈블리어로 작성된 프로그램을 입력으로 받아들이며, 실행에 적합한 형태의 목적 프로그램으로 변환하는 프로그램을 의미한다. 사실 어셈블러는 컴파일러와 입력되는 내용이 다를 뿐, 기능적으로는 유사하다.



- 컴파일러의 조건

(1) 컴파일러는 옮김의 과정에서 프로그램의 뜻을 보존하여야 한다. 입력받은 프로그램의 의미를 충실히 따라야 함을 의미한다. 이런 조건이 없다면 컴파일러를 사용하는 사용자는 이를 믿고 프로그램을 작성할 수 없기 때문이며, 잘못된 옮김에 대한 우려가 존재한다.

(2) 컴파일러는 입력으로 들어온 프로그램을 어떤 면에서든지 개선해야 한다. 예를 들어 소스코드를 기계어로 옮긴다면 기계가 이해할 수 없었던 언어를 기계가 이해할 수 있게 개선하는 것이 된다. 같은 언어로 옮긴 경우에는 성능이 향상되는 등의 장점이 있어야 한다. 이렇게 이행되지 않는다면 컴파일을 수행할 이유가 없어진다.



- 컴파일러의 기능

(1) 고급언어를 기계어로 번역 혹은 변환하는 작업을 수행(C or C++)

(2) JAVA 의 경우 바이트 코드로 변환한다. 중간단계의 코드를 생성하고 이것을 해석해서 실행한다. 그리고 자바의 바이트 코드는 JVM에 의해서 실행된다.



- 컴파일러의 원리

마이크로프로세서(CPU를 단일의 IC칩에 집적한 반도체 소자로 CPU의 여러 형태 중 하나)는 각각 다른 기계어 코드를 가지고 있기 때문에 같은 고급언어라도 다른 기계어 코드를 생성해야 한다. 따라서 개발자는 해당 CPU에 맞는 컴파일러를 사용해야 한다.


하지만, JAVA의 경우 다양한 CPU에 실행하도록 하는 철학을 지니고 개발되었기 때문에 바이트 코드를 가지고 번역하고 해석해서 실행하는 방식이다(WORA). JAVA의 장점은 한번 컴파일된 바이트 코드는 다른 플랫폼에서 재컴파일할 필요가 없이 수행될 수 있다는 이식성 그리고 독립성이 좋다. 그러나 단점은 바이트 코드를 해석해서 실행할 프로그램 구조가 필요하며 직접 기계어 코드를 실행하는 것보다 속도면에서 낮다.



- 컴파일러의 실행단계

많은 수의 컴파일러들이 다음과 같은 순서를 거쳐 소스코드를 번역하지만, 컴파일러 혹은 프로그래밍 언어의 특성에 따라서 일부는 생략 혹은 추가될 수 있다.


(1) 구문분석
소스코드 파일을 읽어 개별 문법 요소(연산자, 괄호, 식별자 등) 단위로 자른 후, 이 문법 요소들을 해석하여 추상구문트리를 생성한다. 이 과정에서 문법에 맞지 않는 소스코드는 사용자에게 알린다.

(2) 최적화
추상구문트리를 분석하여 최적화를 수행한다. 도달할 수 없는 코드를 식별 혹은 산술 표현식을 미리 계산 그리고 루프 풀기 등의 대부분의 최적화가 이 단계에서 수행된다.

(3) 코드생성

최적화된 추상구문트리로부터 목적코드를 생성한다. 목표언어가 기계어일 경우에는 레지스터 할당, 연산 순서 바꾸기 등 하드웨어에 맞는 최적화가 이 단계에서 시행된다. 대부분의 하드웨어 최적화 알고리즘은 NP복잡도를 갖지만 휴리스틱을 통해 많은 최적화가 수행된다.


(4) 링킹
목적코드가 기계어일 경우, 여러 라이브러리 목적 코드를 묶어 하나의 실행 파일을 생성하게 된다. 이 과정은 링커에 의해 수행되며 어떤 이들은 링커를 컴파일러의 일부로 간주하지 않는다.

Posted by doubler
,