알고 가면 좋을 것들.
라이브러리란?
- 프로그램이 동작하기 위해 필요한 외부 목적 코드들
링킹이란?
- 각각의 TU(해석 유닛)들에서 생성된 목적 코드들을 하나씩 모아서 하나의 실행 파일을 만드는 작업
정적 링킹과 동적 링킹
- 정적 링킹 : 정적 라이브러리를 링킹하는 방식
- 동적 링킹 : 동적 라이브러리를 링킹하는 방식
컴파일 과정
전처리 과정이 종료되고 컴파일 과정이 수행됩니다.
해석 유닛 생성
이 단계에서 컴파일이 이루어집니다.
전처리기 토큰들이 컴파일 토큰으로 변환 되고, 컴파일 토큰들은 컴파일러에 의해 해석되어서 해석 유닛(TU)을 생성합니다.
- 예시;
// TU 1
int func(); // 선언
int func() { // 정의
return 1;
}
해석 유닛이 생성될 때 유일 정의 규칙을 따릅니다.
위의 경우는 func()가 1개만 정의되어 있으므로 유일 정의규칙을 위배하지 않습니다
- 예시 2
// TU 1
int func(){ // 정의
return 2;
}
int func() { // 정의
return 1;
}
해석 유닛이 생성될 때 유일 정의 규칙을 따릅니다.
위의 경우는 func()가 2개가 정의되어 있으므로 유일 정의규칙을 위배함으로써 컴파일이 수행되지 않습니다.
목적 코드 생성
컴파일러는 생성된 해석유닛을 바탕으로 목적 코드(ObjectCode)를 생성합니다.
해석 유닛은 컴파일러에 의해서 어셈블리 코드로 번역됩니다.
기계어 명령어에 가까운 형태지만 아직 완전한 실행파일이 아닙니다.
컴파일 전체과정 요약
링킹 과정
마지막 링킹 과정에서는 컴파일러가 생성한 목적 파일들과 외부 라이브러리 파일들을 모아서 실행 파일이 생성됩니다.
링킹 과정이 끝나게 되면, 사용하는 시스템에 따라서 각기 다른 형태의 파일들을 생성하게 됩니다.
윈도우즈는 PE 파일, 리눅스 계열의 시스템의 경우는 ELF라 불리는 형태의 파일을 생성합니다.
링킹 단계는 크게 컴파일된 오브젝트 코드를 하나로 묶는 정적 링킹과, 프로그램 실행 중에 외부 라이브러리를 동적으로 연결하는 동적 링킹이 존재합니다.
정적 링킹
- 정적 라이브러리
- 필요로 하는 라이브러리가 링킹 후에 완성된 프로그램 안에 포함됩니다.
- 실행 파일 자체에 해당 라이브러리 코드가 박혀있기 때문에 정적 (static) 이라고 합니다.
- 정적 라이브러리 만들기
// bar.h
void bar();
// bar.cc
void bar() {};
// foo.h
void foo();
// foo.cc
#include "bar.h"
int x = 1;
int foo(){
bar();
x++;
return 1;
}
-------------------------------
// 컴파일
g++ -c bar.cc foo.cc
// 정적 라이브러리 생성.
ar crf libfoobar.a foo.o bar.o
컴파일로 생성된 bar.o, foo.o 목적 코드들을 하나로 묶어서 정적 라이브러리 libfoobar.a를 생성합니다.
- 실행 파일에서 사용
//main.cc
#include "foo.h"
int main() { foo(); }
// terminal
g++ main.cc libfoobar.a -o main
일반적인 상황이라면 main을 컴파일하면서 실행파일을 생성할 때,
foo.cc 코드와 bar.cc 코드를 같이 컴파일 해서 링킹해야 했습니다.
하지만 이미 foo.cc와 bar.cc가 컴파일되어 있는 libfoobar.a 라는 라이브러리가 존재하므로 다시 컴파일할 필요는 없습니다.
동적 링킹
정적 라이브러리의 문제점
- 정적 라이브러리를 모든 프로그램에서 정적 링크를 수행하게 되면 모든 프로그램의 크기는 정적 라이브러리의 크기만큼 증가합니다.
- 프로그램이 실행되면 프로그램이 메모리에 로드 되는데 모든 프로그램들이 똑같은 정적 라이브러리 코드를 메모리에 올리면 메모리 낭비가 일어납니다.
- 새 버전의 라이브러리가 나와서 이를 시스템에 적용하고 싶을 때 정적 링크 상태라면 이 프로그램들을 다시 컴파일 해야 합니다.
- 정적 라이브러리 전체를 링킹하면 사용하지 않는 모든 함수들까지 전부 다 프로그램에 포함된다는 것입니다.
- 모든 프로그램들이 같은 라이브러리를 링크해도 정적으로 링킹하면 프로그램 내에 동일한 라이브러리 코드를 포함해야 한다는 것입니다.
정적 라이브러리와 동적 라이브러리의 차이
정적 라이브러리의 경우 모든 프로그램에서 libc에 해당하는 코드를 각자가 가지고 있는 형태라면
동적 라이브러리의 경우에는 libc가 하나만 있고 모든 프로그램에서 이를 공유하는 형태입니다.
공유 라이브러리가 메모리에 올라가 있는 상태
프로세스 1, 2, 3의 가상 페이지 테이블에서 각각 다른 주소를 가지고 있지만 해당 페이지 테이블의 주소가 각각 가리키는 주소는 libc의 실제 주소입니다.
즉 실제 메모리에는 libc 라이브러리를 한 군데에만 올려둔 뒤 각 프로세스의 페이지 테이블 내용을 바꿔줌으로써 마치 프로세스 마다 고유의 위치에 libc 코드가 존재하는 것처럼 사용이 가능합니다.
- 동적 라이브러리 생성 예시
// 목적 코드 생성
$ g++ -c -fpic foo.cc bar.cc
// 동적 라이브러리 생성
$ g++ -shared foo.o bar.o -o libfoobar.so
// main.cc
#include "bar.h"
#include "foo.h"
int main()
{
bar();
foo();
}
// main 링크 실행 파일 생성.
g++ main.cc libfoobar.so -g -o main
참고
모두의 코드
C 언어 문법을 아시는 분들이라면, 씹어먹는 C++ 강좌를 통해 C++ 기초 부터 최근의 C++ 17 까지 모든 내용을 배우실 수 있습니다. C 언어와 C++ 의 기본적인 문법이 비슷하기 때문에, C 언어를 어느 정
modoocode.com