정적 라이브러리

mydailylogs
|2022. 12. 5. 11:24

앞선 글에서 링커가 다수의 재배치가능 목적파일들을 읽어들이고 이들을 연결해서 한 개의 출력 실행파일을 만든다고 가정해왔다. 실제로, 모든 컴파일 시스템은 관련된 객체 모듈들을 정적 라이브러리라고 부르는 한 개의 파일로 패키징하는 매커니즘을 제공하며 이 라이브러리는 다음에 링커의 입력으로 제공될 수 있다. 출력 실행파일을 만들 때, 링커는 응용 프로그램이 참조하는 라이브러리 내의 객체 모듈만을 복사한다.

Approach 1 (컴파일러에 의존)

정적 라이브러리가 없다고 하더라도 컴파일러를 통해서 C의 모든 표준함수에 대한 호출을 인식하고 이에 기반한 코드를 작성할 수도 있다. 다만 이러한 방식은 컴파일러에 상당한 복잡성을 제공하고, 함수가 추가되고 삭제될 시에 새로운 컴파일러 버전이 필요하게 될 수도 있다.

unix> gcc main.c /usr/lib/libc.o

 

Approach 2 (다수의 목적파일을 활용한 링킹)

또 다른 접근 방법은 모든 C 표준 함수들을 한 개의 재배치 가능 목적 모듈에 저장해서 응용 프로그래머들이 자신의 실행 파일에 링크하도록 하는 것이다. 이는 앞서 컴파일러에게 과도한 복잡성을 부여했던 문제를 해결할 수는 있으나, 시스템의 모든 실행 파일이 표준 함수들의 모듈 전체의 복사본을 포함하게 되어 디스크 공간을 극도로 낭비하게 될 것이라는 단점이 발생한다(예를 들어 libc.a가 8 MB이고, libm.a은 1 MB 정도이다.). 더욱 문제가 되는 것은 각각 실행되고 있는 프로그램들이 이 함수들의 복사본을 메모리에 모두 로드하게 되어서 메모리가 극도로 낭비될 것이라는 것이다.

unix> gcc main.c /usr/lib/printf.o /usr/lib/scanf.o ...

각각의 표준 함수에 대해서 재배치 가능 파일을 별도로 생성하고, 응용 프로그래머들에게 자신의 실행 파일에 링크하도록 만들 수 있다. 그렇지만 이러한 접근 방식은 응용 프로그래머들에게 명시적으로 적절한 객체 모듈을 자신의 실행파일에 링크할 것을 요구하며, 이 작업은 에러가 생겨나기 쉽고 시간도 낭비된다.

 

Approach 3 (정적 라이브러리)

이와 같은 정적 라이브러리 개념은 이러한 여러 가지 접근 방법의 단점을 해결하기 위해 개발되었다. 연관된 함수들은 별도의 모듈로 컴파일해서 하나의 정적 라이브러리 파일로 패키지화 할 수 있다. 앞서 수많은 재배치 파일을 링커에게 입력 파일로 제공해야 했다면, 이제는 해당 함수들을 포함한 정적 라이브러리를 링커에게 제공해줌으로써 직전 방식의 불편을 해소할 수 있다.

unix> gcc main.c /usr/lib/libm.a /usr/lib/libc.a

링커 시 링커는 오직 프로그램이 참조하는 객체 모듈만을 복사하므로 디스크와 메모리 상의 실행 파일의 크기를 줄일 수 있다. 한편, 응용 프로그래머는 일부 라이브러리 파일의 이름을 include하기만 하면 된다.

Unix 시스템에서 정적 라이브러리는 아카이브라고 알려진 특정 파일 포맷으로 디스크 상에 저장된다. 아카이브는 연결된 재배치 가능 목적파일들의 집합으로, 헤더는 각 목적 파일의 크기와 멤버 함수의 위치를 기술한다. 아카이브 파일 이름은 .a 접미어로 나타낸다. 라이브러리에 대한 우리의 논의를 좀더 구체적으로 하기 위해서 libvector.a라는 정적 라이브러리에서 다음의 addvec.o  multvec.o 에 내장된 벡터 루틴을 제공하길 원한다고 가정하자.

/* addvec.o */
void addvec(int *x, int *y, int *z, int n)
{
    int i;
    
    for (i = 0; i < n; i++)
        z[i] = x[i] + y[i];
}
/* multvec.o */
void addvec(int *x, int *y, int *z, int n)
{
    int i;
    
    for (i = 0; i < n; i++)
        z[i] = x[i] * y[i];
}

 

라이브러리를 생성하기 위해서 다음의 ar 명령어를 활용할 수 있다.

unix> gcc -c addvec.c multvec.c 		# 재배치 목적파일로의 변환
unix> ar rcs libvector.a addvec.o multivec.o 	# 하나의 아카이브 파일로 합침

 

이러한 아키아브 파일은 다음과 같이 응용 프로그램에서 활용될 수 있다.

/* main.c */
#include <stdio.h>
#include "vector.h"

int x[2] = {1, 2};
int y[2] = {3, 4};
int z[2];

int main()
{
    addvec(x, y, z, 2);
    printf("z = [%d %d]\n", z[0], z[1]);
    return 0;
}

 

실행 파일을 만들기 위해서는 입력 파일 main.o와 libvector.a를 컴파일하고 링크한다.

unix> gcc -c main.c
unix> gcc -static -o program main.o ./libvector.a

이때 -static 인자는 컴파일러 드라이버에게 메모리에 적재될 수 있고 로드 시에 추가적인 링킹 과정 필요없이 돌아가도록 완전히 링크된 실행가능 목적파일을 링커가 만들어야 한다는 것을 의미한다.

 

이때 링커가 실행되면 링커는 addvec.o에서 정의된 addvec 심볼이 main.o에서 참조되는지 결정해야 하며, main.o에 addvec이 활용된다는 것을 심볼 해석과 재배치과정을 거쳐 알고 addvec.o를 실행파일에 복사한다. 이때 핵심은 프로그램이 multvec.o에 의해 정의된 심볼들을 참조하지 않기 때문에 링커는 이 모듈을 실행파일에 복사하지 않는다는 것이다. 링커는 또한 C 런타임 시스템으로부터 다른 모듈들과 함께 printf.o 모듈을 libc.a에서 복사한다.

 

'CS' 카테고리의 다른 글

L7 프로토콜 - HTTP  (0) 2023.08.08
IPv4에서 IPv6로의 전환 매커니즘  (0) 2023.08.06
실행가능한 목적파일  (0) 2022.12.03
Linking(링킹) 소개  (0) 2022.12.02
시간 지역성을 위한 캐시 재배치  (0) 2022.12.01