본문 바로가기

iOS

(iOS) Library vs Framework(1)

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