동적 라이브러리 vs 정적 라이브러리

프로그램은 자신이 직접 짠 코드에 여러 라이브러리를 섞어서 만든다.
섞는 방식은 두 가지가 있다.
하나는 정적 라이브러리로 만드는 것이고, 다른 하나는 동적 라이브러리로 만드는 것이다.
shared_vs_static 위의 그림이 두 라이브러리의 차이를 잘 보여준다.
라이브러리가 다른 프로그램에서 많이 사용 된다면 동적 라이브러리로 만드는 것이 좋다.
하지만 동적라이브러리를 사용하면, 처음 시작할 때 해당 라이브러리의 위치를 파악하는데 오래 걸린다.
그래서 포토샵 같은 프로그램은 시작 할 때 오래 걸린다.
본 포스팅에서는 예제를 통해 static library와 shared library를 만들 것이다.
예제 코드 : https://github.com/luckydipper/c_cpp_compile_process/tree/main/make_library

시작하기 앞서, library를 만들 때 이름에 주의 해야한다.
파일 이름은 lib{name}.a 또는 lib{name}.so로로 하면 좋다.
clang과 gcc에서 -l flag로 library를 linking 할 때에는 파일 이름 앞에 lib이 적혀있어야 하기 때문이다.
또한 lib가 앞에 붙은 so 파일은 링크 시간에 사용되는 library로 설계한다.
만약 앞 부분에 lib가 없는 so 파일일 경우 runtime에 plug in 되는 library이다.

목차

1.정적 라이브러리

2.동적 라이브러리

3. 출처

정적 라이브러리 $($static library$)$

링킹 할 때, 해당 라이브러리 전체를 실행 파일에 링킹한다.

정적 라이브러리 만드는 방법

예제 파일 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  make_library/
  ├── Makefile
  ├── add.cpp
  ├── add.hpp
  ├── add.o
  ├── cube.o
  ├── div.cpp
  ├── div.hpp
  ├── div.o
  ├── mul.cpp
  ├── mul.hpp
  ├── mul.o
  ├── sub.cpp
  ├── sub.hpp
  └── sub.o
  결과 파일 arithmetic_operation.a

static Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CC = clang++-9
flag = -c

add.o : add.cpp add.hpp
	$(CC) $(flag) add.cpp

mul.o : mul.cpp mul.hpp
	$(CC) $(flag) mul.cpp

sub.o : sub.cpp sub.hpp
	$(CC) $(flag) sub.cpp

div.o : div.cpp div.hpp
	$(CC) $(flag) div.cpp

arithmetic_operation.a : add.o mul.o sub.o div.o
	ar r arithmetic_operation.a add.o mul.o sub.o div.o

ar의 r flag는 replace existing or insert new files into the archive

사칙 연산 파일 예시

1
2
//add.hpp
int add(int a, int b);
1
2
3
4
//add.cpp
int add(int a, int b){
    return a + b;
}

정적 라이브러리 살펴보는 방법

1
2
3
4
5
6
7
ar -tv make_library/arithmetic_operation.a

//결과
rw-r--r-- 0/0    960 Jan  1 00:00 1970 add.o
rw-r--r-- 0/0    960 Jan  1 00:00 1970 mul.o
rw-r--r-- 0/0    960 Jan  1 00:00 1970 sub.o
rw-r--r-- 0/0    960 Jan  1 00:00 1970 div.o

정적 라이브러리 링킹하여 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
make_library/
├── main.cpp
├── cube.cpp
├── cube.hpp
└── arithmetic_operation.a

// 실행 명령어
clang++-9 make_library/main.cpp make_library/cube.cpp make_library/arithmetic_operation.a -o main.o
// 혹은
clang++-9 make_library/cube.o make_library/main.o make_library/arithmetic_operation.a -o linking_main_cube_arithimetic.out
// 혹은
clang++ -o linking_main_cube_arithimetic.out main.o cube.o -Lpath/to/libarithmetic_operation.a -larithmetic_operation // 이때 확장자와 앞의 lib은 붙히지 않음.

자세한 파일 내용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// main.cpp
#include <cstdio>
int add(int a, int b);
int mul(int a, int b);
int sub(int a, int b);
int div(int a, int b);
int cube(int a);

int main()
{
    int example_num = 1;
    example_num = add(example_num,example_num); // 2 = 1 + 1
    example_num = mul(example_num,example_num); // 4 = 2 * 2
    example_num = sub(example_num, 1);          // 3 = 4 - 1
    example_num = div(example_num, 3);          // 1 = 3 / 3
    example_num = sub(example_num, 2);          // -1 = 1 -2 
    example_num = cube(example_num);            // -1 = -1 * -1 * -1
    printf("%i", example_num);
    //-1
    return example_num;
}
1
2
3
4
5
6
// cube.cpp
#include "cube.hpp"
int cube(int a)
{
    return a*a*a;
}

동적 라이브러리 $($shared library$)$

링킹 할 때 라이브러리를 링킹 할 수도 있고, 실행중에 메모리에 올려서 사용 할 수도 있다.

동적 라이브러리 만드는 방법

파일 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
make_library/
├── Makefile
├── add.cpp
├── add.hpp
├── add_.o
├── div.cpp
├── div.hpp
├── div_.o
├── libarithmetic_operation.so // 만들어짐
├── main.cpp
├── main.o
├── mul.cpp
├── mul.hpp
├── mul_.o
├── sub.cpp
├── sub.hpp
└── sub_.o

shared Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CC = clang++-9
flag = -c -fPIC -o  # postion independent code

add_.o : add.cpp add.hpp
	$(CC) $(flag) add_.o add.cpp 

mul_.o : mul.cpp mul.hpp
	$(CC) $(flag) mul_.o mul.cpp

sub_.o : sub.cpp sub.hpp
	$(CC) $(flag) sub_.o sub.cpp

div_.o : div.cpp div.hpp
	$(CC) $(flag) div_.o div.cpp

libarithmetic_operation.so : add_.o mul_.o sub_.o div_.o
	clang++-9 -shared -o libarithmetic_operation.so add_.o mul_.o sub_.o div_.o

링킹하여 실행 하는 법

1
2
3
4
5
6
clang++-9 -o print_-1.out main.o cube.o -L./ -larithmetic_operatio
// print_-1.out이 만들어짐!

./print_-1.out
error while loading shared libraries: libarithmetic_operation.so: cannot open shared object file: No such file or directory
//위와 같은 에러가 뜰 것이다.

호출할 라이브러리의 위치를 알아야 한다.
/etc/ld.so.conf 이 위치가 dll을 찾아보는 위치를 담고 있다.
혹은 환경변수 LD_LIBRARY_PATH를 활용하여 만들 수 있다.

1
export LD_LIBRARY_PATH=~/desktop/c_cpp_compile_process/make_library

동적 라이브러리 살펴보는 방법

1
2
3
ldd make_library/print_-1.out
// export 명령어 치기 전 결과 : libarithmetic_operation.so => not found
// export 명령어 친 후 결과 : libarithmetic_operation.so => /home/qhrqufdlek/desktop/c_cpp_compile_process/make_library/libarithmetic_operation.so
1
2
# ln -s libmycalcso.so.1.0.1 libmycalcso.so
shared library를 symbolic link 하면, version constrol하기 쉽다. <br>

Execution time에 메모리에 올려 실행하는 방법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <dlfcn.h>
// 써야 하는 함수의 prototype

void *dlopen (const char *filename, int flag);
// 일반 명명규칙(lib*.so)을 따르지 않아도 되고, LD_LIBRARY_PATH 환경변수에 directory를 추가하지 않아도 된다.
// 오류 발생시 NULL이 return된다.

const char *dlerror(void);

void *dlsym(void *handle, const char *symbol);
// symbol(함수 또는 전역변수)의 위치에 대한 pointer를 얻습니다.
// handle : return 받은 shared object handle
// symbol : shared object에서 찾고자 하는 symbol
// return 함수를 찾는데 NULL이 return됐으면 error. 아니면 dlerror로 찾음.

dlfcn.h 라이브러리를 사용하는 파일은 -ldl로 컴파일 해야한다. libdl.so를 이용하기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//run_time_linking.cpp
include <dlfcn.h>
#include <stdio.h>

int main(int argc, char **argv) 
{
    int one = 1;
    int result;
    char *error;
    int (*ptr_sum)(int, int) = NULL;

    void *handle = dlopen ("/home/qhrqufdlek/desktop/c_cpp_compile_process/make_library/libarithmetic_operation.so", RTLD_LAZY);

    if(handle == NULL) 
    {
        printf("%s\n", dlerror());
        return 1;
    }
    ptr_sum = (int (*)(int, int))dlsym(handle, "_Z3addii");
    //아니 name mangling 된 것까지 symboltable 보고 가져와야 함?
    printf("%s\n", dlerror());
    if((error = dlerror()) != NULL) 
    {

        printf("error\n");
        dlclose(handle);
        return 1;
    }

    printf("%i \n", ptr_sum(one, one));
    dlclose(handle);

    return 0;
}
1
2
  // command
  clang++-9 -o runtime_linking.out -ldl runtime_link.cpp

!주의! dlsym$($handle, "_Z3addii"$)$에서 함수의 이름이 _Z3addii가 됐다.
이는 c++이 name mangling$($overloading, 같은 이름의 함수 사용하기 위해 함수 이름을 변환 시킴$)$ 때문이다.

1
2
3
// 2가지 command로 해당 함수에 해당하는 symbol을 볼 수 있다.
nm libarithmetic_operation.so |grep add 
readelf -a libarithmetic_operation.so

를 통해 so file 안의 symbol talbe에서 add 함수가 있는 곳을 알 수 있다.
혹은 shared library를 만들 때, extern “C” keyward를 활용하여, name mangling을 막아야 한다.

1
  extern "C" int test_dl_func();

출처

씹어 먹는 c++
embedded recipy
https://stackoverflow.com/questions/6561273/is-liblibrary-name-a-so-a-naming-convention-for-static-libraries-in-linux
https://www.joinc.co.kr/w/Site/C/Documents/CprogramingForLinuxEnv/Ch12_module
https://yaaam.tistory.com/entry/Linux-%EB%8F%99%EC%A0%81-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%A1%9C%EB%93%9C-dlopen-dlsym-dlclose-dlerror
https://modoocode.com/321
https://www.it-note.kr/186
https://man7.org/linux/man-pages/man3/dlopen.3.html
https://umbum.dev/494
https://stackoverflow.com/questions/18096596/using-dlsym-in-c-without-extern-c

Comments