C/C
[C/C++] 공유 라이브러리 (Shared Library) 기본 예제
2026. 4. 13. 00:51반응형
기본 개념
공유 라이브러리 (Shared Library)의 기본 개념은 프로그램이 필요로 하는 코드를 실행 파일 안에 직접 넣지 않고, 별도 파일로 분리해서 여러 프로세스가 함께 공유하는 방식이다. 반대 개념인 정적 라이브러리(Static Library) 와 비교하면 이해가 쉽다.

요약하면 아래와 같다.
- 정적(.a): lib 코드가 실행파일 안에 복사되어 프로세스마다 각자 보유
- 공유(.so): lib 코드가 외부에 분리되어 여러 프로세스가 메모리 한 곳을 함께 참조
공유 라이브러리 하면 .so 파일을 반드시 알아야한다. .so 파일은 Shared Object의 약자로, Linux/Android에서 공유 라이브러리의 파일 형식이다.
| 플랫폼 | 확장자 | 예시 |
| Linux / Android | .so | libmylib.so |
| Windows | .dll | mylib.dll |
| macOS | .dylib | libmylib.dylib |
.so 파일 이름 규칙
lib mylib .so .1 .0
↑ ↑ ↑ ↑ ↑
접두사 이름 확장 major minor
실제로 파일 3개가 심볼릭 링크로 연결이 된다.
libmylib.so → libmylib.so.1 # 링커가 사용 (-lmylib)
libmylib.so.1 → libmylib.so.1.0 # 런타임이 사용 (SOVERSION)
libmylib.so.1.0 # 실제 파일
메모리 공유 구조

핵심 포인트 두 가지이다.
- 코드 영역 (text) - read-only라서 두 프로세스가 물리 메모리의 같은 페이지를 그대로 공유한다. .so 가 100개 프로세스에서 로드돼도 물리 메모리엔 딱 1벌만 존재합니다.
- 데이터 영역 (data) - 전역변수 등은 프로세스마다 별도로 복사된다 (Copy-on-Write). 한 프로세스가 값을 바꿔도 다른 프로세스에 영향을 주지 않는다.
소스코드
프로젝트 구조
mylib/
├── CMakeLists.txt
├── mylib.h
├── mylib.cpp
├── main_dl.cpp
└── main.cpp
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(mylib_example CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 1. shared library
add_library(mylib SHARED mylib.cpp)
target_include_directories(mylib PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
# -fPIC는 SHARED 타겟이면 CMake가 자동으로 붙여줌
set_target_properties(mylib PROPERTIES
OUTPUT_NAME "mylib" # → libmylib.so
VERSION 1.0.0
SOVERSION 1 # → libmylib.so.1 심볼릭 링크
)
# 2. 실행파일
add_executable(app main.cpp)
target_link_libraries(app PRIVATE mylib)
# 3. 빌드 후 .so를 실행파일과 같은 디렉토리에 위치시키기
set_target_properties(app PROPERTIES
BUILD_RPATH "$ORIGIN" # 실행 시 같은 디렉토리에서 .so 탐색
)
# dlopen 버전 실행파일
add_executable(app_dl main_dl.cpp)
target_link_libraries(app_dl PRIVATE
${CMAKE_DL_LIBS} # -ldl (플랫폼에 맞게 자동 처리)
)
set_target_properties(app_dl PROPERTIES
BUILD_RPATH "$ORIGIN"
)
라이브러리 헤더 파일 (mylib.h)
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
// 초기화 / 해제
void mylib_init();
void mylib_destroy();
// 기능 함수
int mylib_add(int a, int b);
void mylib_greet(const char* name);
#ifdef __cplusplus
}
#endif
라이브러리 구현 파일 (mylib.cpp)
#include "mylib.h"
#include <iostream>
#include <stdexcept>
static bool g_initialized = false;
void mylib_init() {
if (g_initialized) return;
std::cout << "[mylib] init\n";
// 리소스 할당, DB 연결, 하드웨어 초기화 등
g_initialized = true;
}
void mylib_destroy() {
if (!g_initialized) return;
std::cout << "[mylib] destroy\n";
g_initialized = false;
}
int mylib_add(int a, int b) {
if (!g_initialized)
throw std::runtime_error("mylib not initialized!");
return a + b;
}
void mylib_greet(const char* name) {
if (!g_initialized)
throw std::runtime_error("mylib not initialized!");
std::cout << "[mylib] Hello, " << name << "!\n";
}
사용 측 main 함수 (main.cpp)
#include "mylib.h"
#include <iostream>
int main() {
mylib_init();
mylib_greet("AOSP developer");
int result = mylib_add(10, 32);
std::cout << "10 + 32 = " << result << "\n";
mylib_destroy();
return 0;
}
사용 측 main 함수 (main_dl.cpp) (동적 로딩 방식으로 런타임 중에 .so를 로딩하고 싶은 경우)
#include <dlfcn.h>
#include <iostream>
int main() {
void* handle = dlopen("./libmylib.so", RTLD_LAZY);
if (!handle) { std::cerr << dlerror(); return 1; }
auto init = (void(*)()) dlsym(handle, "mylib_init");
auto add = (int(*)(int,int)) dlsym(handle, "mylib_add");
auto destroy = (void(*)()) dlsym(handle, "mylib_destroy");
init();
std::cout << add(3, 4) << "\n"; // 7
destroy();
dlclose(handle);
return 0;
}
빌드 및 실행
# 빌드 디렉토리 생성 (out-of-source 빌드 권장)
cmake -S . -B build
cmake --build build
# 실행 (정적 링킹 방식)
./build/app
# 실행 (동적 로딩 방식)
cd build
.app_dl
출력
Static linking at load time / Implicit linking 방식 (main.cpp, app 실행 파일)
[mylib] init
[mylib] Hello, AOSP developer!
10 + 32 = 42
[mylib] destroy
Dynamic loading / Explicit linking 방식
[mylib] init
7
[mylib] destroy
일반 링크 (정적 링킹) vs 동적 로딩 방식 비교
Shared Library를 로딩하는데 두 가지 방식이 있다고 했다. 이 둘의 방식을 비교해본다.
| 항목 | 일반 링크 방식 | dlopen 방식 |
| 다른 이름 | Static linking at load time / Implicit linking | Dynamic loading / Explicit linking |
| 링크 시점 | 컴파일 타임 (-lmylib) | 런타임 (dlopen() 호출 시) |
| 로드 시점 | 프로세스 시작 시 자동 로드 | 코드에서 명시적으로 호출할 때 |
| .so 없으면 | 프로세스 시작 자체가 실패 | dlopen 반환값이 NULL (핸들링 가능) |
| 함수 호출 | 함수명 직접 호출 mylib_init() | 함수 포인터로 호출 fp() |
| 헤더 파일 | 필요 (#include "mylib.h") | 불필요 (심볼 이름 문자열로 접근) |
| 컴파일 의존성 | 라이브러리 헤더/바이너리 필요 | 없음 |
| 심볼 해석 | 링커가 자동 처리 | dlsym()으로 직접 탐색 |
| 메모리 해제 | 프로세스 종료 시 자동 | dlclose() 명시적 호출 |
| 코드 복잡도 | 단순 | 함수 포인터 선언 필요, 복잡 |
| 주요 용도 | 일반적인 라이브러리 사용 | 플러그인, 모듈 핫스왑, 선택적 기능 |
| 대표 사례 | libstdc++, glibc | Android HAL, 플러그인 시스템 |
로드 타이밍을 비교해본다면 아래와 같다.
일반 링크 방식
─────────────────────────────────────────────
프로세스 시작
│
├─ ld.so 가 libmylib.so 탐색
├─ 없으면 → 즉시 종료 (error)
├─ 있으면 → 메모리 로드 & 심볼 연결
│
└─ main() 진입 ← 이미 .so 로드 완료
dlopen 방식
─────────────────────────────────────────────
프로세스 시작
│
└─ main() 진입 ← .so 아직 로드 안 됨
│
├─ dlopen() 호출 시점에 로드
├─ 없으면 → NULL 반환 (계속 실행 가능)
│
└─ dlclose() 호출 시점에 해제
언제 어떤 방식을 쓰는게 좋을까?
일반 링크 방식이 적합한 경우
- 항상 필요한 라이브러리 (없으면 동작 불가)
- 코드를 단순하게 유지하고 싶을 때
- 성능이 중요한 경우 (함수 포인터 오버헤드 없음)
dlopen 방식이 적합한 경우
- 플러그인 / 모듈 시스템 (런타임에 확장)
- 선택적 기능 (.so 없어도 기본 동작은 유지)
- 여러 버전의 .so 중 하나를 선택해서 로드
- Android HAL 처럼 하드웨어별 구현체를 교체
AOSP 기준으로는 /vendor/lib/hw/ 안의 HAL 모듈들이 dlopen 방식으로 로드되는 대표적인 사례이다.
반응형
'C > C' 카테고리의 다른 글
| [C/C++] 구조체와 공용체 (Struct와 Union)에 대한 고찰 2 - Flexible Array Member(가변 배열 멤버, FAM) (0) | 2026.04.09 |
|---|---|
| [C][소켓] socket read 중 close() 시 안전하게 구현하기 (0) | 2026.03.25 |
| [C][소켓] 메인스레드가 recv 하는 동안 fd가 close되면 어떻게 될까? (0) | 2024.09.04 |
| [C/C++] __attribute__((packed))에 대한 이해 (1) | 2024.03.28 |
| [C] 파일입출력 예제 (0) | 2023.03.31 |
