Computer Science/Operating System

컴파일(Compile), 링킹(Linking), 로딩(Loading), 런타임(Runtime)

rueMi 2024. 4. 17. 17:49

메모리 주소 바인딩에 대해 공부하기 전 알아야 하는 개념들에 대해 먼저 살펴보자. 컴파일, 링킹, 런타임, 로딩에 대해 하나 하나 살펴볼 것이다.

컴파일, Compile

컴파일이란 원시 코드를 컴퓨터가 이해할 수 있는 언어로 번역하는 과정을 말한다.

 

컴퓨터는 0과 1만 이해할 수 있다. 우리가 흔히 사용하는 Java, C++은 고급 프로그래밍 언어로, 컴퓨터 입장에서는 외계어와 같다. 따라서 우리가 짠 소스코드를 컴퓨터가 이해할 수 있는 언어로 번역하는 과정이 필요한데, 그 과정을 컴파일이라 한다.

단계별 역할

컴파일을 단계별로 정리하면 다음과 같다.

 

위 과정에 대해 하나씩 정리해보자.

Preprocessor, 전처리기

‘#’으로 시작하는 부분, 즉 #include, #define과 같은 부분을 소스 코드로 변경하여, 확장된 소스 코드를 반환한다.

#include  파일을 포함시켜 달라는 의미이므로, 해당 헤더 파일에서 코드를 복붙해준다.
#define 정의한 값 또는 식으로 치환해준다.

 

예를 들어 C++에서 ‘#include <iostream>’이라는 구문이 있다면 <iostream> 라이브러리를 include 시키고, 코드에 ‘<< cout’이라는 구문이 있으면 전처리기는 <iostream> 라이브러리의 ‘<< cout’ 함수 코드를 복붙해준다.

 

이러한 과정을 통해 확장된 소스 코드를 컴파일러로 넘겨준다.

Compiler

컴파일러는 전처리기로 확장된 소스코드를, 기계어에 근접한 어셈블리어로 번역해준다.

Assembler

어셈블리어를 기계어로 번역해준다. 이 단계에서 생성된 파일을 목적 파일(object file)이라 한다.

 

여기까지를 보통 컴파일이라고 칭한다.

Linker

여러 개의 object file을 하나로 합쳐서 exe 파일로 만드는 과정이다.

 

작성된 소스코드가 사용하는 OS API(System call) 또는 표준 라이브러리를 연결시켜서 exe 파일로 만든다. 실행 파일(exe)은 타겟 파일(target file)이라고도 한다.

 

 

즉, 모든 걸 종합해서 보면 하나의 프로젝트에 여러 개의 소스코드 파일이 있으면 각 소스코드마다 (원시 코드 - 확장 코드 - 어셈블리 코드 - 목적 파일)이 생성되고, Linker가 모든 목적 파일과 라이브러리를 종합해 하나의 타겟 파일을 만드는 것이다.

 

여기서 원시코드부터 목적파일 까지를 ‘Compiling’, 목적파일 ~ 타겟 파일까지를 ‘Linking’이라 부른다. Compiling 구간만을 컴파일로 보는 관점을 ‘좁은 의미의 컴파일’이라 하고, ‘Compiling + Linking’ 전 과정을 컴파일로 보는 관점을 ‘넓은 의미의 컴파일’이라고 한다.

 

아래 그림은 컴파일의 전 과정을 이해하기 쉽게 그린 그림이다.

Static Linking vs Dynamic Linking

위에서 설명한 Linking 과정은 정적 링킹(Static Linking) 방법이다. 정적 링킹과 동적 링킹에 대해 알아보자.

Static Linking, 정적 링킹

하나의 실행 파일 안에 연결된 라이브러리 코드가 전부 다 들어있는 링킹 방식이다.

하나의 실행 파일을 만들 때 사용되는 모든 코드를 다 넣었기 때문에 또 다른 라이브러리는 필요하지 않다. 따라서 실행 시 속도는 빠르지만, 경우를 생각해보면 이는 심각한 메모리 낭비를 가져온다.

심각한 메모리 낭비를 가져오는 상황

 

수십명의 유저가 리눅스 서버에 접속해 동시에 정적 링킹으로 만들어진 실행 파일을 실행시키는 상황을 생각해보자. 실행 파일은 자신의 이름을 출력해주는 아주 간단한 프로그램이다. 여기서 출력하는 데 사용된 cout이라는 클래스 라이브러리가 정적으로 링킹되어 있다. 이 말은 실행 파일에 cout 클래스 라이브러리 코드가 다 들어가 있다는 뜻이다. 즉, 50명이 이를 동시에 실행시키면 똑같은 cout 코드가 메모리에 50번 적재되는 것과 같다.

 

이 대안으로 등장한 것이 바로 동적 링킹이다.

Dynamic Linking, 동적 링킹

많이 쓰이는 라이브러리 함수를 메모리에 한 번만 올리고, 프로그램이 라이브러리 함수를 호출할 때 메모리에 있는 함수 주소로 점프해 실행한 후 다시 돌아오도록 하는 방법이다.

이러한 동적 링크 라이브러리를 윈도우에서는 DDL(Dynamic Link Library)라 부르고, 유닉스나 리눅스에서는 Shared Library라 부른다.

 

동적 링킹을 사용하면 정적 링킹을 사용할 때처럼 메모리 낭비를 가져오지는 않지만, 점프 과정에서 약간의 오버헤드가 생기고 점프를 위한 코드가 추가되어야 한다는 단점이 있다.

 

실생활에서의 정적 링킹과 동적 링킹의 비교

게임을 샀는데 게임의 라이브러리 일부분이 버전 업데이트 된 경우를 생각해보자.

 

만약 게임이 정적 링킹으로 만들어 졌다면 버전up된 게임을 새로 구매해야 한다. 왜냐하면 실행 파일 안에 라이브러리 코드가 다 들어있기 때문이다.

하지만 게임이 동적 링킹으로 만들어졌다면 게임을 새로 사는 것이 아니라 DDL만 업데이트 하면 된다. 왜냐하면 동적 링킹은 코드와 라이브러리를 별도로 분리해놓기 때문이다.

 

이와 같이 지속적인 유지 및 보수가 필요하고, 업데이트가 빈번한 프로그램에 대해서는 동적 링킹이 적합하다.


로딩, Loading

로딩은 데이터를 메모리로 옮기는 것을 말한다. 컴파일 완료된 프로그램을 실행시키면 .exe에 있는 파일이 메모리에 올라가게 되는데 이를 로딩이라고 한다.

 

로딩에도 static loading과 dynamic loading이 있다.

static loading

모든 코드를 메모리에 옮기는 것이다.

 

하지만 이 방법은 메모리 낭비가 심하기 때문에 쓰이지 않는다. 실제로 프로그램이 실행될 때 쓰이는 기능은 일부분이기 때문이다.

dynamic loading

코드의 부분, 즉 routine이 call 되어야 메모리에 올라간다.

 

해당 방식을 많이 사용한다.


런타임, Runtime

런타임이란 사용자에 의해 응용프로그램이 동작 되어지는 때, 즉 ‘실행중’인 상태를 말한다.

런타임 오류

런타임 오류란 말 그대로 런타임 환경에서 오류가 발생하는 것을 말한다.

 

만약 어떤 소스코드가 실행 가능한 프로그램으로 컴파일 되었다 할지라도, 이것은 여전히 프로그램 실행 중에 버그를 일으킬 수 있다. 예를 들어 프로그램은 0으로 나누는 연산 등 예상치 못한 오류가 발생하면 중단되는데, 이러한 것들은 컴파일은 성공하더라도 프로그램이 실행된 후인 런타임 환경에서 오류가 발생한다.

런타임 시 동적 링킹 : DDL로의 점프

위에서 살펴본 동적 링킹도 런타임에 수행된다.

 

동적 링킹에서는 프로그램이 실행된 후, 라이브러리 함수가 호출될 때, 해당 DDL로 점프한다고 했었다. 이 과정은 프로그램이 실행된 후, 즉 런타임에 수행된다.

 

그렇기 때문에 동적 링킹에 필요한 DDL이 존재하지 않는다면 어떻게 될까? 일단 프로그램이 실행되긴 하겠지만, 라이브러리 함수를 만나면 점프할 주소가 없으므로 런타임 오류가 발생할 것이다.


참고자료

 

[ 운영체제 ] 컴파일(Compile)과 링킹(Linking), 런타임(Runtime), 로딩(Loading)

이 글에서는 주소 바인딩(Address Binding)을 이해하기 위해 꼭 알아야 하는 용어들을 설명한다. 원래 주소 바인딩을 바로 다루려고 했으나 정리하다가 용어가 헷갈려서 공부할겸 정리한다. 컴파일,

charles098.tistory.com