우리가 정성들여서 코드를 짜도 사실 컴퓨터는 읽을 수 없다. 컴퓨터는 기계어만 읽을 수 있기 때문에 우리는 코드를 짠 후 꼭 번역하는 과정을 거쳐야 한다. 우리가 처음 코딩을 배울 때 프로그래밍 언어를 설치한다고 하는데 이는 대부분 컴파일러나 인터프리터 등을 설치하는 것이라고 볼 수 있다.
컴파일러: 번역을 한 번에 다 한 후 실행시키는 경우
인터프리터: 번역을 한 줄 한 줄씩 바로 하면서 실행하는 경우
C와 C++이 대표적인 컴파일 언어이고, 파이썬이 대표적인 인터프리터 언어이다. 컴파일러는 컴파일하는 시간이 따로 소요된다. 만약 대규모 프로그램을 수정할 일이 생긴다면 수정 하고 컴파일이 오래 걸려서 유지보수가 힘들어 진다. (그래서 컴퓨터 여러대를 연결해서 컴파일 하는 경우가 있다고도 들었다) 하지만 컴파일이 완료된 후 실행파일과 인터프리터를 비교하면 아무래도 실행시간 자체는 컴파일러가 더 빠르다고 할 수 있다.
JIT(Just-in-time compilation)컴파일
(=동적 번역)
자바 가상머신, .NET(C#), V8(node.js), PyPy에서 지원
1. (컴파일러) 1차적으로 소스코드를 바이트코드로 바꿈
2. (인터프리터) 코드가 실행되는 과정에서 JIT 컴파일러가 바이트 코드를 읽고 기계어 실시간으로 생성
인터프리터 언어가 못하는 코드 최적화 과정을 바이트코드 컴파일러가 해줌. 캐싱 같은걸로 같은 함수 여러번 불릴 때마다 기계어 코드 생성하는 과정 줄여줌. 그리고 실행과정에서 컴파일을 해서 운영체제 최적화 할 수도 있고 플랫폼 이식성이 좋다.
이렇게 여러가지 번역기들이 있지만 가장 기본이 되는 컴파일러와 컴파일 과정에 대해 알아보자
compile : 명령어를 번역하다
전처리기가 아래와 같은 일을 하면서 소스코드를 확장한다.
source.c 에서 source.i가 된다. (intermediate)
#include <stdio.h>
#define SENTINEL 1
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int result = MAX(x, y);
#ifdef DEBUG
printf("Debugging is enabled.\n");
#endif
로그 레벨 설정하는 느낌
// <- 이런 부분을 지운다.
만약 전처리 결과를 보고싶으면 GCC 컴파일러에서 옵션 -E를 주면 된다
gcc -E 파일이름.i
아래 이미지가 지금 만들고 있는 파일을 -E 옵션으로 띄운 것이다.
원래 파일의 첫째줄이 #include "rbtree.h"
인데
그 부분이 직접 헤더파일으로 바뀐것을 확인할 수 있다.
이 때 소스코드가 어셈블리어로 변한다.
source.i 에서 source.s가 된다 s는 어셈블리를 나타냄
만약 컴파일 결과를 보고싶으면 GCC 컴파일러에서 옵션 -S를 주면 된다
gcc -S 파일이름.c
이 명령의 결과로 파일이름.s가 만들어진다.
위 파일을 어셈블리리로 바꾼 결과는 아래와 같다.
어셈블리어를 기계어로 바꾸는 과정.
GCC 명령어는
gcc -c 파일이름.s
결과로 파일이름.o
가 생성됐다.
목적파일이 생성되었다. 지금은 다른파일과 합쳐지지 않은 독립적인 상태로 존재한다.
링크 과정은 링커라는 프로그램이 하는데 보통 컴파일 과정과 분리되어 있다.
gcc 파일이름.c
이걸 하면 목적파일이 만들어지고 그 목적파일로
gcc 파일이름.o
이 명령어를 치면 우리가 흔히 아는 .exe 실행파일이 만들어진다.
이 단계는 내 코드가 다른 라이브러리의 함수를 쓴다면 이루어지는 과정이다. 우리는 마치 우리가 만든 것만 쓴다고 생각하겠지만 사실 많은 C 표준 라이브러리 함수를 쓰고 있다. C의 printf()
도 라이브러리 안에 있는 함수이다. include <stdio.h>
문장을 빼고 printf()
를 실행하면 아마 오류가 발생할 것이다. 그 외에 malloc같은 동적할당 함수, strlen과 같은 문자열 함수 등등도 다 표준 라이브러리가 제공하는 함수다.
표준 라이브러리는 C 컴파일러가 제공한다. GCC외의 새로운 컴파일러를 만들고 싶다면 표준 라이브러리를 다 구현해야 할 것이다. 그 표준은 ISO/IEC 9899:2018 문서에 정리되어 있다.
아무튼 이런 라이브러리나 다른 목적파일들을 함께 써야할 때 링크가 이루어진다. 링커는 필요한 파일들만 가져와서 주소 공간에 오브젝트들을 배열시킨다. 이렇게 프로그램 정보와 코드, 데이터섹션, 라이브러리 정보들을 적어 실행파일로 만들어두면, 그 파일이 실행될때 운영체제가 그 정보들을 가지고 코드와 데이터를 메모리에 로드하고 CPU가 프로그램 로직을 실행 할 것이다.
링크 과정은 링커라는 프로그램이 하는데 보통 컴파일 과정과 분리되어 있다.
gcc 파일이름.c
이걸 하면 목적파일이 만들어지고 그 목적파일로
gcc 파일이름.o
이 명령어를 치면 우리가 흔히 아는 .exe 실행파일이 만들어진다.