[Week1 - CSAPP] 1.2 - 프로그램은 다른 프로그램에 의해 다른 형태로 번역된다.

2025. 9. 5. 22:10CSAPP

컴파일러와 인터프리터 - 기계를 위한 번역기

컴퓨터는 0,1 밖에 이해하지 못하는 외계인 같은 존재이다.

 

이를 위해 우리는 우리가 작성한 코드를 일종의 번역기를 통해 컴퓨터에게 전달 해야한다.

 

이러한 번역기는 크게 컴파일러인터프리터로 나뉜다.

 

이들은 보통 OS 의존성을 지닌다.

 

다만, VM(가상 머신)을 함께 쓴다면, OS로 부터 독립성을 가질 수 있다.

  • 컴파일러 or 인터프리터가 바이트 코드로 변경 → 이를 VM이 실행
  • 하지만 VM은 성능 저하를 일으킬 수 있다.
  • 대표적으로 OS를 쓰는 언어는 Java(JVM)와 C#(.NET CLI)이 있다.
    • → 예 : 윈도우에서 배포된 ‘.exe’ 파일은 Linux 환경에서 실행 할 수 없다

 

 

컴파일러 - 사용자를 위한 최고의 번역기

 

컴파일러는 개발자가 입력한 코드 전체파일 단위4단계의 과정을 거쳐 번역한다


(1) 전처리 단계 - 복붙, 삭제, 치환

  • 이 단계에서는 전처리 지시문(예 : #include, #define)이 처리된다.
  • 또한 주석이 모두 삭제 되고, 컴파일러가 읽을 수 있는 형태의 텍스트로 변환한다.
    • → #include같은 경우 그 지시문의 자리에 외부 파일 텍스트를 그대로 삽입(복붙과 같다. 완전히 같다.)한다.
      • → 이로 인해 #include<파일 A>를 여러 번 작성 시, 파일A를 다중정의하는 문제가 생길 수 있다.
      • → 또한 헤더에 함수, 전역 변수를 정의 하는 경우에도 다중 정의가 될 수 있다.
    • → #define 같은 메크로들을 치환 한다.
    • → #if / #ifdef 같은 조건부 코드들을 선택 / 제거한다.

(2) 컴파일 단계 - 쪼개기 & 분석하기

  • 이 단계에서는 [ int, main, ( ]같은 단위로 쪼개는 토큰화를 하는 어휘 분석
    • → 코드 전체를 단어들로 쪼갠다고 생각하면 좋다.

 

  • 문장들을 검사하여 문법 오류를 잡아내고 트리 구조로 표현하는 구문 분석
    • 문법 검사 + 개요 작성이라고 생각하면 좋다.

 

  • 타입 일치 여부, 일관성(예 : 선언되지 않는 변수를 사용), 함수 호출(파라미터가 올바른지 검사), 범위 검사(지역 변수가 그 지역 안에서만 사용되었는지 검사), 제약 조건 검사(예 : return가 함수 밖에서 사용되지는 않았는지 검사)를 하는 의미 분석
    • → 문법 검사와 달리, 코드가 말이 되는지 검사하는, 일종의 맥락 검사라고 보면 좋다.

 

  • 더 효율적인 코드로 다듬는 중간 코드 생성 & 최적화
    • → 이 부분은 좀 이해하기 어려울 수 있다.
a = b + c * d;

 

이런식으로 작성 된 코드를

 

t1 = c * d
t2 = b + t1
a = t2

 

이런 식의 계산 식의 계산 순서를 명확하게 표현하는 단계 라고 생각하면 좋다.


(2-5) 컴파일 단계와 어셈블리 단계의 사이 - 어셈블리어로 바꾸기

  • 이 단계에서는 최적화된 중간 코드CPU 명령어의 집합인 어셈블리어로 바꾼다.
  • → 어셈블리어는 CPU 명령어와 1 : 1로 대응 된다.
mov eax, 5   ; eax 레지스터에 5 저장
add eax, 3   ; eax에 3 더하기
  • 이런식으로 어셈블리어는 단순히 기계어를 사람이 이해하기 쉽게(???) 바꾼 형태이다.(사실 쉽지는 않은것 같다만… 기계어와 다르게 읽을 수는 있는 그런 언어다)
  • 여담으로 CPU의 종류(x86, ARM, RISC-V등등..)에 따라 어셈블리어는 달라진다.
    • → 그로 인해 같은 C언어 코드여도 이 단계에서 바뀐 어셈블리어가 다를 수도 있다.

(3) 어셈블리 단계 - 기계어로 변환

  • 이 단계 부터는 이제 사람이 읽을 수 없다.
  • 위 단계에서 생성한 어셈블리어를 CPU가 바로 이해 가능한 기계어로 바꾸는 단계 이다.
mov eax, 5  ; eax 레지스터에 5 저장
add eax, 3  ; eax에 3 더함

 

이 어셈블리어 코드를

 

B8 05 00 00 00
83 C0 03

 

이렇게 바꾼다.

 

  • 또한 이 단계에서는 CPU 아키텍처(x86, ARM등등..)에 맞게 명령어 데이터의 위치를 결정( = 메모리 주소 배치)한다.
  • 또한 목적 파일을 생성한다.
    • → 목적 파일은 기계어 코드 + 심볼 테이블로 변환한다.
      • → 심볼 테이블은 컴파일러 내부의 자료 구조로
항목 내용
이름(Name) 변수/함수 이름 (x, main)
종류(Type) 변수, 함수, 클래스 등 어떤건지
자료형(Data Type) int, float, char 등
범위(Scope) 전역, 지역 등
주소(Address) 메모리 위치 또는 레지스터
속성(Attribute) 상수, 매개변수, 반환값 등
  • 이런식으로 있다.

(4) 링크 단계 - 목적 파일 + 라이브러리로 최종 파일 생성

  • 이 단계에서는 목적파일과 라이브러리를 합쳐 .exe나 .out 같은 실행 파일로 만드는 단계 이다.
  • 목적 파일에서 참조한 함수, 변수의 주소를 실제 주소로 연결하는 심볼 결합
    • → 예 :printf 호출 → C표준 라이브러리의 printf의 기계어 코드와 연결
  • 변수와 함수의 실제 메모리 주소를 결정하는 주소 할당
  • 아래와 같은 라이브러리들을 포함 시키는 라이브러리 포함
    • 외부 라이브러리(.lib, .a, .dll, .so 등) 함수와 연결
    • 정적 라이브러리: 코드 그대로 포함
    • 동적 라이브러리: 실행 시 참조
  • 이 단계를 거치면 OS에서 바로 실행이 가능한 파일이 완성된다.  → .exe, .out 등등... 
  • 또한 CPU가 직접 실행할 수 있다.

지금까지의 과정을 요약하면 다음과 같다.

단계 역할
전처리 텍스트 가공, 헤더 붙이기, 치환
컴파일 의미 분석, 중간 코드(IR) 생성 & 최적화
어셈블 어셈블리 → 목적 코드(.o)
링크 목적 코드 + 라이브러리 → 실행 파일 완성

 

컴파일러는 코드 파일 전체를 번역 하기에, 컴파일 하는 시간은 길지만 실행 속도는 빠르다.

  • → 개발 속도는 지연되지만, 사용자가 쓸 때는 빠르다.

 

인터프리터 - 개발자를 위한 최고의 번역기

 

  • 인터프리터는 컴파일러와 같이 토큰화, 분석의 과정을 거치지만, 코드를 한 줄 또는 한 블록씩 실행 한다.
  • 또한 컴파일러와 달리 실행 파일을 생성하지 않고 즉시 실행한다.
    • 이 과정들이 인터프리터의 가장 큰 장점과 단점을 만든다.
    • 장점 : 즉시 실행하고, 한 줄씩 실행하기에 디버깅 하기 용이하다.
    • → 개발에 편리하다고 할 수 있다.
    • 단점 : 즉시 실행하기에, 속도가 느리다.

  • 컴파일러 = 요리사가 레시피 전체를 읽고 요리
  • 인터프리터 = 요리사가 레시피를 한 줄씩 읽고 바로바로 요리

로 생각 하면 좋다. 각각의 장단점이 있으니, 잘 선택하길 바란다.