1. 컴퓨터와 사람 간 소통
고수준 언어와 저수준 언어의 차이
- 고수준 언어 : 추상화되어 있는 언어
- 저수준 언어 : 세부 사항에 신경써야 하는 언어
매개변수(parameter)와 함수
- 개별적인 세부 사항만 다소 차이가 있을 뿐, 계속 반복되는 경우와 같은 상황에서 쓰이는데, 이러한 차이를 매개변수라고 함
- 별도로 분리하고 매개변수를 제외한 나머지 명령어를 하나로 묶어 코드로 지정하면 됨
정리
- 모든 프로그래밍 언어는 특정 구문에 따라 작성됨
- 컴파일러는 언어 구문에 따라 코드 구문을 분석해 구문 트리로 만들고,
- 이 구문 트리를 C/C++ 언어처럼 기계 명령어로 번역하여 CPU로 직접 넘기거나
- 자바처럼 바이트 코드로 변환한 후 가상 머신으로 넘겨 실행함
- 고급 언어는 추상적 표현이 뛰어나서 사용하기 쉽지만, 저수준 계층에 대한 제어 능력이 떨어짐
- 따라서 직접 저수준 계층의 세부 사항을 제어할 수 있어야 하는 운영 체제 중 일부분은 어셈블리어로 작성됨
2. 컴파일러의 작동
컴파일러
: 고수준 언어를 저수준 언어로 번역하는 프로그램
- 컴파일러는 크게 보면 번역기, 작게 보면 텍스트 처리 프로그램임
컴파일 과정
1. 각각의 토큰 추출하기
: 컴파일러는 먼저 각 항목을 잘게 쪼개는데, 각 항목이 가지고 있는 추가 정보를 함께 묶어 관리한다. “키워드 / ‘int’라는 종류” <<와 같은 추가 정보 두 개가 포함된다.
- 소스 코드를 돌아다니며 모든 토큰을 찾아내는 작업
이렇게 소스 코드에서 토큰을 추출하는 과정을 ‘어휘 분석(Lexical Analysis)’이라고 함
2. 토큰이 표현하고자 하는 의미
: 토큰 추출 후에도, 프로그래머가 전달하고자 하는 토큰 의도를 표현해야 함

- 필요한 토큰이 정확히 오는지 확인하며 문법 오류를 체크하고, 해야 하는 작업을 확인하는 작업 → 해석(Parsing)
- 해석해 낸 구조는 트리로 표현하며, 이는 ‘구문 트리’라고 하고, 이 트리를 생성하는 전체 과정을 ‘구문 분석’이라고 함
3. 의미 분석
: 생성된 구문 트리에 이상이 없는지 확인해야 한다.
- 이 단계를 통과하면 프로그램에 이상이 없기 때문에 컴파일 오류가 없다는 것이 증명되는데, 이 과정을 ‘의미 분석(Semantic Analysis)’이라고 함
4. 중간 코드 생성하기
: 의미 분석이 끝나면 컴파일러는 구문 트리를 탐색한 결과를 바탕으로 좀 더 다듬어진 형태인 ‘중간 코드’를 생성한다.
- 어떤 경우에는 중간 코드에 추가적인 최적화가 진행되기도 함
- ex. 순환 구문 내 순환 상태와 관계없이 계산 가능한 값이 있다면, 이런 계산은 순환 구문 외부에서 먼저 진행될 수 있음
5. 코드 생성
: 위 과정이 완료되면 컴파일러는 앞의 중간 코드를 어셈블리어 코드로 변환하고, 마지막으로 컴파일러는 이 어셈블리어 코드를 기계 명령어로 변환한다.
끝? 링커!
- 컴파일 과정을 거쳐 생성된 기계 명령어 데이터는 code.o라는 파일에 저장되는데, 이때, .o 확장자를 가지는 파일을 ‘대상 파일(Object File)’이라고 한다.
- 소스 파일에는 각각의 대상 파일이 있고, 프로젝트가 복잡해서 소스 파일이 여러 개 있다면 대상 파일도 여러 개가 된다. 하지만, 우리는 하나의 실행 파일을 원하고, 이를 합쳐주는 무언가가 필요하다.
- 이렇게 대상 파일을 병합하는 작업은 ‘링크(Link)’라고 하며, ‘링커(Linker)’라는 프로그램이 수행한다.
3. 링커
‘정적 라이브러리’와 ‘동적 라이브러리’?
1. 링커는 이렇게 일한다
: 컴파일러가 생성한 대상 파일 여러 개를 하나로 묶어 최종 실행 파일을 생성한다.
소스 코드 (func.c) → 대상 파일 (func.o) → 실행 파일 (.exe)
여러 대상 파일을 한 곳에 모아 실행 파일로 만들어 주는 것이 링커의 일!
- 심벌 해석
- 책의 특정 장이 다른 장의 내용을 참고할 떄가 있는데, 이는 우리가 작성하는 프로그램이 다른 모듈의 프로그램이 인터페이스 또는 변수를 참조하는 것과 같음
- 이를 종속성이 있다고 표현함
- 링커는 종속성이 올바른지 확인하는 역할을 함
- 실행 파일 생성
- 재배치
- 뒤에 무슨 내용이 올 지 모름 : ‘N쪽을 확인하세요 → 100쪽을 확인하세요’처럼 초기 내용을 정확한 내용으로 다시 수정하는 것
- 컴파일 시점에는 함수가 어느 메모리 주소에 위치할 지 모르기 때문에 처음에는 N쪽으로 표시해놓고, 나중에 실제 메모리 주소로 대체함
2. 심벌 해석 : 수요와 공급
: 심벌은 전역 변수와 함수의 이름을 포함하는 모든 변수 이름을 의미한다.
- 링커는 ‘전역 변수’에 관심을 가짐 (Not 지역 변수)
- 소스 파일에 다른 모듈에서 참조할 수 있는 심벌과
- 소스 파일이 다른 모듈에서 정의한 심벌을 참조한다는 정보를 알아야 함
링커에게 넘기기 전 컴파일러

: 컴파일러는 기계 명령어 뿐만 아니라 이 명령어를 실행시키는 데이터도 생성하는데, 이 데이터는 대상 파일에 반드시 포함되어야 한다.
컴파일러가 생성하는 대상 파일에 중요한 두 가지 영역이 있음
- 명령어 부분 : 소스 파일에 정의된 함수에서 변환된 기계 명령어가 저장되는 부분으로, ‘코드 영역(Code Section)’이라고 함
- 데이터 부분 : 소스 파일의 전역 변수가 저장되는 부분으로, ‘데이터 영역(Data Section)’이라고 함
컴파일러는 소스 파일마다 외부에서 참조 가능한 심벌이 어떤 것인지 그 정보를 기록하고, 반대로 어떤 외부 심벌을 참조하고 있는지 기록한다.
- 이러한 표를 ‘심벌 테이블’이라고 함
- 기본적으로 심벌 테이블은 공급과 수요라는 두 가지 내용만 표현함
- 내가 정의한 심벌, 즉 다른 모듈에서 사용할 수 있는 심벌
- 내가 사용하는 외부 심벌
- 컴파일러는 생성한 심벌 테이블을 대상 파일에 저장함
3. 정적 라이브러리, 동적 라이브러리, 실행 파일
두 가지 프로젝트의 코드가 많아질수록, 관련 코드를 구현하기 위해 어떤 함수를 사용해야 하는지 찾아내는 것이 점점 힘들어진다.
-
정적 라이브러리, 정적 링크

-
다른 팀의 코드를 별도로 컴파일한 후 패키지로 묶고, 구현된 모든 함수의 선언을 포함하는 헤더 파일을 제공하는 방식
-
소스 파일 여러 개를 미리 개별적으로 컴파일하고 링크하여 정적 라이브러리로 생성할 수 있음
- 소스 파일마다 단독으로 컴파일 한다는 점을 기억
-
이후 실행 파일을 생성할 때는 자신의 코드만 컴파일하며, 미리 컴파일이 완료된 정적 라이브러리는 다시 컴파일할 필요 없이 링크 과정에서 그대로 실행 파일에 복제됨

속도는 빠르지만, 동일한 코드와 데이터의 복사본을 갖게 되므로 디스크와 메모리 낭비가 심해질 수 있음

→ 동적 라이브러리 사용!