iOS에서 모듈화를 하나씩 공부해 보면서 Library와 Framework를 자연스럽게 접하게 되는데 그 안에서도 Static, Dynamic으로 나눠서 사용하고 있어 정확히 어떤 형태의 라이브러리 또는 프레임워크를 이용해야 할지 확실히 몰라 정리해보고자 한다.
작성한 코드가 실행 가능한 파일이 되기까지..
일단 iOS예시를 먼저 보기 전 C를 예로 들어보자. 아래와 같이 add라는 함수를 사용하는 코드가 있다고 해보자.
// add.h
#ifndef ADD_H_
#define ADD_H_
int add(int a, int b);
#endif
// add.c
#include "add.h"
int add(int a, int b) {
return a + b;
}
// hello.c
#include <stdio.h>
#include "add.h"
int main(void) {
printf("3 + 4 = %d\n", add(3, 4));
return 0;
}
먼저 hello.c를 gcc로 컴파일하여 hello.o(오브젝트)파일로 만든 후 심볼을 살펴보면 다음과 같다.
$ gcc -c hello.c -o hello.o
$ nm hello.o
U _add
0000000000000000 T _main
U _printf
0000000000000050 s l_.str
0000000000000000 t ltmp0
0000000000000050 s ltmp1
위처럼 코드에서 작성된 _main 함수는 T로 Text(code) 영역에 저장되어 있어 호출이 가능하지만, _printf, _add함수의 경우 U로 되어있다. 이는 심볼이 정의되어 있지 않음을 의미하며 Linking 과정에서 호출될 address를 지정해주어야 한다. 일반적으로 링킹과정에서 이 주소를 채우는 것을 resolve한다고 한다.
현재 상황에서는 이미 위의 add.c 파일을 보면 add함수에 대한 구현 코드가 있으므로 컴파일하여 add.o 파일을 만든 후 링킹 과정을 통해 실행 파일로 만들 수 있다.
$ gcc -c add.c -o add.o
$ gcc -o hello hello.o add.o
$ nm hello
0000000100000000 T __mh_execute_header
0000000100003f78 T _add
0000000100003f28 T _main
U _printf
링킹을 마친 후(gcc에서 ld(링커)를 이용해서 링킹 과정을 수행해 줌) 실행파일인 hello의 심볼을 살펴보면 _add에 대한 address가 resolve된 것을 확인할 수 있다.
하지만 매번 개발 시 add가 필요할 때마다 컴파일하여 object파일을 만들고 다시 링킹 하는 과정을 모두 반복할 필요는 없다. 라이브러리를 만들면 이러한 컴파일 과정 없이 링킹 과정만 거쳐 실행파일 또는 다른 object파일을 만들 수 있다.
라이브러리(Library)
위키백과의 라이브러리에 대한 설명은 다음과 같다.
라이브러리(영어: library)는 주로 소프트웨어를 개발할 때 컴퓨터 프로그램이 사용하는 비휘발성 자원의 모임이다. 여기에는 구성 데이터, 문서, 도움말 자료, 메시지 틀, 미리 작성된 코드, 서브루틴(함수), 클래스, 값, 자료형 사양을 포함할 수 있다. OS/360 및 이후 세대에서는 파티션 데이터 세트로 부른다.
즉 코드, 함수, 클래스, 값, 자료형 등을 재사용할 수 있도록 하기 위해 컴파일된 파일이라고 생각하면 된다. 이러한 라이브러리의 종류는 링킹 방식에 따라 정적(Static) 라이브러리와 동적(Dynamic) 라이브러리로 나뉜다.
처음 hello.c 파일을 컴파일한 후 심볼을 보았을 때 정의 유무와 상관없이 header(.h)파일에 정의된 함수가 hello.o의 심볼에 포함된 것을 확인할 수 있다. 이 프로그램 실행 시 링킹 방식에 따라 프로그램 실행 시점(처음 메모리에 로드되는)에 이미 address가 resolve 되어있거나, 또는 런타임(해당 함수가 필요해져 호출될 때) resolve될 수 있다.
Static Library와 Static Linking
Static Library를 만드는 방법은 간단하다. add.o 파일을 add.a 파일로 만들어보자.
$ ar rcs add.a add.o
$ file add.a
add.a: current ar archive random library
$ nm add.a
add.a(add.o):
0000000000000000 T _add
0000000000000000 t ltmp0
0000000000000020 s ltmp1
현재는 add.o파일 하나만 추가했기 때문에 심볼 확인 시 add.o에 대한 심볼만 표시되지만, 만약 여러 오브젝트파일을 함께 압축하여 정적 라이브러리로 만들 경우 여러 오브젝트 파일에 대한 심볼을 보여준다. sub에 대한 object파일을 만들어 포함시킬 경우에는 아래처럼 심볼이 추가됨을 확인할 수 있다.
// sub.h
#ifndef SUB_H_
#define SUB_H_
int sub(int x, int b);
#endif
// sub.c
#include "sub.h"
int sub(int a, int b) {
return a - b;
}
$ gcc -c sub.c -o sub.o
$ ar rcs libcalc.a add.o sub.o
$ nm libcalc.a
libcalc.a(add.o):
0000000000000000 T _add
0000000000000000 t ltmp0
0000000000000020 s ltmp1
libcalc.a(sub.o):
0000000000000000 T _sub
0000000000000000 t ltmp0
0000000000000020 s ltmp1
이제 생성된 라이브러리를 hello에서 사용하기 위해 hello 코드를 다음과 같이 수정해 주고..
// hello.c
#include <stdio.h>
#include "add.h"
#include "sub.h"
int main(void) {
printf("3 + 4 = %d\n", add(3, 4));
printf("4 - 3 = %d\n", sub(4, 3));
return 0;
}
컴파일 및 링킹을 진행해 주자
$ gcc -o hello hello.c -I. -L. -lcalc
$ ./hello
3 + 4 = 7
4 - 3 = 1
$ nm hello
0000000100000000 T __mh_execute_header
0000000100003f4c T _add
0000000100003ecc T _main
U _printf
0000000100003f6c T _sub
위의 Bash 명령어에서 -I(대문자 i)는 포함하고자 하는 헤더파일의 경로, -L은 포함하고자 하는 라이브러리 경로 -l(소문자 L)은 라이브러리 이름을 나타낸다. 여기서 라이브러리 이름은 libcalc.a 인데 앞의 lib와 뒤의 확장자(.a)는 제외한 calc만 -l 플래그의 인자로 전달해 주면 된다.
심볼을 확인해 보면 _printf는 계속 정의되지 않은 것을 확인할 수 있다. 이는 stdio.h에 정의되어 있으며 libc에 구현이 되어있을 것으로 예상된다.(정확하지 않음). 따라서 프로그램 실행 후 printf가 호출될 때 다이나믹 링커에 의해 libc(또는 다른 동적 표준 C 라이브러리)에 있는 주소로 resolve 되어 실행 될 것이다.
다음은 동적 라이브러리를 만들고 사용하는 방법을 봐보자.
동적 라이브러리(Dynamic Library)
동적 라이브러리(또는 공유 라이브러리 == shared library라고도 함)는 gcc에서 다음의 명령어로 만들 수 있다.
$ gcc -shared add.o sub.o -o libshared_calc.so
$ file libshared_calc.so
libshared_calc.so: Mach-O 64-bit dynamically linked shared library arm64
$ nm libshared_calc.so
0000000000003f78 T _add
0000000000003f98 T _sub
이제 이 다이나믹 라이브러리(.so)를 이용해 hello_dynamic을 만들어보자.
$ gcc hello.c -o hello_dynamic -I. -L. -lshared_calc
$ nm hello_dynamic
0000000100000000 T __mh_execute_header
U _add
0000000100003efc T _main
U _printf
U _sub
$ ./hello_dynamic
3 + 4 = 7
4 - 3 = 1
실행까지 동일하게 잘 되는 것을 확인할 수 있다. 그럼.. 정적 라이브러리와 뭐가 다를까...
libshared_calc.so 파일을 지운 후 hello_dynamic을 실행해 보자.
$ rm libshared_calc.so
$ ./hello_dynamic
dyld[28884]: Library not loaded: libshared_calc.so
Referenced from: /Users/ever/study/LibraryAndFramework/hello_dynamic
Reason: tried: 'libshared_calc.so' (no such file), '/usr/local/lib/libshared_calc.so' (no such file), '/usr/lib/libshared_calc.so' (no such file), '/Users/ever/study/LibraryAndFramework/libshared_calc.so' (no such file), '/usr/local/lib/libshared_calc.so' (no such file), '/usr/lib/libshared_calc.so' (no such file)
[1] 28884 abort ./hello_dynamic
링커에 지정한 경로 '/Users/ever/study/LibraryAndFramework/hello_dynamic/' 그리고 '/usr/local/lib/', '/usr/lib/' 로부터 libshared_calc.so파일을 찾을 수 없다는 에러를 출력한다.
정적 라이브러리의 경우 바이너리 실행 전 이미 호출해야 할 심볼에 대한 주소를 이미 알고 있어 그 주소를 그대로 사용해도 되지만, 동적 라이브러리의 경우 실행 전에는 주소가 Undefined 되어있고 실행 후 동적으로 Resolve 시켜주어야 한다. 만약 그 시점에 참조하는 동적 라이브리가 경로에 존재하지 않을 경우 위처럼 에러를 출력하고 프로그램이 종료된다.
이번 글에서는 정적 라이브러리와 동적 라이브러리의 링킹 방식 및 실행 시점에 어떻게 동작하는지에 대해서 간단히 알아보았다. 다음 글에서는 동적 정적 라이브러리의 장단점, 함께 사용했을 때의 예시, 그리고 프레임워크에 대해서 좀 더 알아보기로 하자.
reference
- https://stackoverflow.com/questions/29714300/what-does-the-rcs-option-in-ar-do
'iOS' 카테고리의 다른 글
(iOS) 유니플로거 리팩토링(2) 튜토리얼 (0) | 2023.06.07 |
---|---|
(iOS) 유니플로거 리팩토링(1) XCFramework (4) | 2023.05.07 |
(iOS) Library vs Framework(3) (0) | 2023.04.06 |
(iOS) Library vs Framework(2) (0) | 2023.03.07 |
Swift import 알아보기 (0) | 2023.01.30 |