C++ Linking | "링킹" 가이드
이 글의 핵심
C++ Linking에 대한 실전 가이드입니다.
들어가며
링킹(Linking)은 오브젝트 파일(.o)을 하나의 실행 파일이나 라이브러리로 결합하는 컴파일의 마지막 단계입니다. 컴파일러는 각 .cpp를 독립적으로 기계어로 변환하고, 링커가 이들을 연결하여 최종 실행 파일을 생성합니다.
1. 링킹 기본
컴파일과 링킹
# 1단계: 컴파일 (소스 → 오브젝트)
# -c: 컴파일만 수행 (링킹 안함)
# -o: 출력 파일 이름 지정
g++ -c main.cpp -o main.o # main.cpp → main.o (기계어 코드)
g++ -c util.cpp -o util.o # util.cpp → util.o (기계어 코드)
# 각 .cpp 파일이 독립적으로 .o 파일로 변환됨
# 이 단계에서는 함수 호출이 아직 연결 안됨 (심볼만 기록)
# 2단계: 링킹 (오브젝트 → 실행 파일)
# 여러 .o 파일을 하나의 실행 파일로 결합
# main.o의 함수 호출을 util.o의 함수 정의와 연결
g++ main.o util.o -o myapp
# 링커가 심볼 해석, 재배치, 최종 실행 파일 생성
# 또는 한 번에: 컴파일 + 링킹
# 내부적으로는 위의 2단계를 자동으로 수행
g++ main.cpp util.cpp -o myapp
파일 구조:
// util.h
#pragma once
int add(int a, int b);
// util.cpp
#include "util.h"
int add(int a, int b) {
return a + b;
}
// main.cpp
#include <iostream>
#include "util.h"
int main() {
std::cout << add(10, 20) << std::endl;
return 0;
}
링킹 과정
1. 심볼 해석 (Symbol Resolution)
- main.cpp의 add() 호출을 util.o의 add() 정의와 연결
2. 재배치 (Relocation)
- 최종 메모리 배치에 맞게 주소/오프셋 조정
3. 최종 이미지 생성
- 실행 파일 또는 라이브러리(.so/.dll) 생성
2. 정적 링킹
정적 라이브러리 생성
# 1. 오브젝트 파일 생성
g++ -c lib.cpp -o lib.o
# 2. 정적 라이브러리 생성 (.a)
# ar: 아카이브 도구 (여러 .o 파일을 하나의 .a 파일로 묶음)
# r: 파일 추가/교체
# c: 아카이브 생성 (없으면)
# s: 인덱스 생성 (심볼 테이블)
ar rcs libmylib.a lib.o
# 결과: libmylib.a (정적 라이브러리)
# 3. 사용: 정적 라이브러리를 링크하여 실행 파일 생성
# -L.: 현재 디렉토리에서 라이브러리 검색
# -lmylib: libmylib.a 링크 (lib 접두사, .a 접미사 자동 추가)
g++ main.cpp -L. -lmylib -o myapp
# 링커가 libmylib.a의 코드를 myapp에 포함 (정적 링킹)
예제:
// lib.h
#pragma once
int multiply(int a, int b);
// lib.cpp
#include "lib.h"
int multiply(int a, int b) {
return a * b;
}
// main.cpp
#include <iostream>
#include "lib.h"
int main() {
std::cout << multiply(5, 6) << std::endl; // 30
return 0;
}
특징:
- ✅ 배포 간단 (단일 실행 파일)
- ✅ 빠름 (런타임 로딩 없음)
- ❌ 크기 큼 (라이브러리 코드 포함)
- ❌ 업데이트 시 재링크 필요
3. 동적 링킹
동적 라이브러리 생성
# Linux/macOS
# 1. 위치 독립 코드 (PIC) 컴파일
# -fPIC: Position Independent Code
# 메모리 어디에 로드되든 실행 가능한 코드 생성
# 공유 라이브러리는 여러 프로세스가 다른 주소에 로드하므로 필수
g++ -fPIC -c lib.cpp -o lib.o
# 2. 동적 라이브러리 생성 (.so)
# -shared: 공유 라이브러리로 링크
g++ -shared lib.o -o libmylib.so
# 결과: libmylib.so (Shared Object)
# 3. 사용: 동적 라이브러리를 링크하여 실행 파일 생성
# -L.: 현재 디렉토리에서 라이브러리 검색
# -lmylib: libmylib.so 링크 (심볼 정보만 기록)
# -Wl,-rpath,.: 링커 옵션 전달
# -rpath: 런타임 라이브러리 검색 경로를 실행 파일에 포함
# .: 현재 디렉토리를 검색 경로로 추가
g++ main.cpp -L. -lmylib -Wl,-rpath,. -o myapp
# 4. 실행
./myapp
# 런타임에 동적 링커가 libmylib.so를 메모리에 로드
# 또는: 환경 변수로 라이브러리 경로 지정
LD_LIBRARY_PATH=. ./myapp
# LD_LIBRARY_PATH: 런타임 라이브러리 검색 경로
# Windows
# 1. DLL 생성 (Dynamic Link Library)
# -shared: 공유 라이브러리로 컴파일
g++ -shared lib.cpp -o mylib.dll
# 결과: mylib.dll (Windows 동적 라이브러리)
# 2. 사용: DLL을 링크하여 실행 파일 생성
# -L.: 현재 디렉토리에서 라이브러리 검색
# -lmylib: mylib.dll 링크 (lib 접두사 생략, .dll 자동 추가)
g++ main.cpp -L. -lmylib -o myapp.exe
# 실행 시 mylib.dll이 같은 디렉토리에 있어야 함
특징:
- ✅ 크기 작음 (라이브러리 공유)
- ✅ 메모리 효율 (여러 프로세스 공유)
- ✅ 업데이트 쉬움 (재컴파일 불필요)
- ❌ 런타임 로딩 필요
- ❌ 버전 관리 복잡
4. 자주 발생하는 문제
문제 1: undefined reference
# 에러 메시지
undefined reference to `add(int, int)'
# 원인: 함수 구현 누락
해결:
# 1. 오브젝트 파일 추가
g++ main.o missing.o -o myapp
# 2. 라이브러리 추가
g++ main.o -lmissing -o myapp
# 3. 소스 파일 추가
g++ main.cpp missing.cpp -o myapp
문제 2: 라이브러리 순서
# ❌ 순서 잘못 (libA가 libB에 의존)
g++ main.o -lB -lA -o myapp
# ✅ 의존성 순서 (의존하는 쪽이 앞)
g++ main.o -lA -lB -o myapp
문제 3: 라이브러리 경로
# ❌ 경로 없음
g++ main.o -lmylib -o myapp
# error: cannot find -lmylib
# ✅ 경로 지정
g++ main.o -L./lib -lmylib -o myapp
g++ main.o -L/usr/local/lib -lmylib -o myapp
문제 4: rpath 설정 (동적 라이브러리)
# ❌ 실행 시 라이브러리 못 찾음
$ ./myapp
error while loading shared libraries: libmylib.so: cannot open shared object file
# ✅ rpath 설정 (실행 파일에 검색 경로 포함)
g++ main.o -L./lib -lmylib -Wl,-rpath,./lib -o myapp
# ✅ LD_LIBRARY_PATH 설정
export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH
./myapp
# ✅ 시스템 경로에 설치
sudo cp libmylib.so /usr/local/lib/
sudo ldconfig
5. 심볼 확인
nm 명령어
# nm: 심볼 테이블 확인 도구
# 실행 파일이나 오브젝트 파일의 심볼(함수, 변수) 목록 출력
# 심볼 목록: 모든 심볼 출력
nm myapp
# 정의된 심볼 (T: text section)
# -g: 외부 심볼만 (global)
# grep " T ": 코드 영역에 정의된 심볼만 필터링
nm -g myapp | grep " T "
# 미정의 심볼 (U: undefined)
# -u: 링킹이 필요한 외부 심볼만 출력
nm -u myapp
# 예시 출력:
# 0000000000401136 T main
# 0000000000401136: 메모리 주소
# T: Text section (코드 영역에 정의됨)
# main: 심볼 이름
# 0000000000401156 T _Z3addii (mangled name)
# _Z3addii: C++ 이름 맹글링 (add(int, int))
# U printf@@GLIBC_2.2.5
# U: Undefined (외부 라이브러리에서 제공)
# printf@@GLIBC_2.2.5: glibc 버전 2.2.5의 printf
ldd 명령어 (동적 라이브러리 의존성)
# ldd: 동적 라이브러리 의존성 확인 (Linux)
# 실행 파일이 필요로 하는 모든 공유 라이브러리 출력
$ ldd myapp
linux-vdso.so.1 (0x00007fff...)
# linux-vdso: 커널 시스템 콜 최적화 (가상 라이브러리)
libmylib.so => ./lib/libmylib.so (0x00007f...)
# libmylib.so: 우리가 만든 라이브러리
# => ./lib/libmylib.so: 실제 파일 경로
# (0x00007f...): 메모리 로드 주소
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)
# libc.so.6: C 표준 라이브러리 (printf, malloc 등)
/lib64/ld-linux-x86-64.so.2 (0x00007f...)
# ld-linux: 동적 링커 (런타임에 라이브러리 로드)
objdump 명령어
# objdump: 오브젝트 파일 분석 도구 (디버깅용)
# 디스어셈블: 기계어 → 어셈블리 변환
# -d: disassemble (코드 영역 디스어셈블)
objdump -d myapp
# 실행 파일의 기계어 코드를 어셈블리로 출력
# 함수별 어셈블리 코드 확인 가능
# 심볼 테이블: 모든 심볼 출력
# -t: symbol table
objdump -t myapp
# nm과 유사하지만 더 자세한 정보 제공
# 동적 심볼: 동적 링킹에 사용되는 심볼만
# -T: dynamic symbol table
objdump -T myapp
# 런타임에 로드되는 공유 라이브러리의 심볼 출력
6. 링크 타임 최적화 (LTO)
LTO 사용
# LTO 활성화 (Link Time Optimization)
# -flto: 링크 타임에 전체 프로그램 최적화 수행
g++ -flto main.cpp util.cpp -o myapp
# 동작:
# 1. 컴파일 시 중간 표현(IR, Intermediate Representation) 생성
# 2. 링크 시 모든 파일의 IR을 함께 분석
# 3. 함수 인라이닝, 데드 코드 제거 등 전역 최적화
# 장점: 성능 향상 (10-20%)
# 단점: 빌드 시간 증가
# 최적화 레벨 조합
# -flto: 링크 타임 최적화
# -O3: 최고 수준 컴파일 타임 최적화
g++ -flto -O3 main.cpp util.cpp -o myapp
# -O3와 -flto를 함께 사용하면 최대 성능 달성
# 릴리스 빌드에 권장
효과:
- 전체 프로그램 최적화
- 인라인 확장 (파일 경계 넘어)
- 데드 코드 제거
- 성능 향상 (5-15%)
단점:
- 컴파일 시간 증가
- 메모리 사용 증가
- 디버깅 어려움
7. 실전 예제: 프로젝트 빌드
Makefile
# Makefile
CXX = g++
CXXFLAGS = -std=c++17 -Wall -g
LDFLAGS = -L./lib
LDLIBS = -lmylib
OBJS = main.o util.o
myapp: $(OBJS)
$(CXX) $(OBJS) $(LDFLAGS) $(LDLIBS) -o myapp
main.o: main.cpp util.h
$(CXX) $(CXXFLAGS) -c main.cpp
util.o: util.cpp util.h
$(CXX) $(CXXFLAGS) -c util.cpp
clean:
rm -f $(OBJS) myapp
.PHONY: clean
CMake
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyApp)
set(CMAKE_CXX_STANDARD 17)
# 정적 라이브러리
add_library(mylib STATIC lib.cpp)
# 실행 파일
add_executable(myapp main.cpp util.cpp)
# 링크
target_link_libraries(myapp mylib)
# 동적 라이브러리
add_library(mylib_shared SHARED lib.cpp)
set_target_properties(mylib_shared PROPERTIES OUTPUT_NAME mylib)
# rpath 설정
set_target_properties(myapp PROPERTIES
INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib"
BUILD_WITH_INSTALL_RPATH TRUE
)
정리
핵심 요약
- 링킹: 오브젝트 파일을 실행 파일로 결합
- 정적 링킹: 라이브러리 포함 (크기 큼, 빠름)
- 동적 링킹: 런타임 로드 (크기 작음, 공유)
- 심볼 해석: 함수 호출을 정의와 연결
- LTO: 전체 프로그램 최적화
정적 vs 동적 링킹
| 특징 | 정적 링킹 | 동적 링킹 |
|---|---|---|
| 파일 | .a (Linux), .lib (Windows) | .so (Linux), .dll (Windows) |
| 크기 | 큼 (라이브러리 포함) | 작음 (참조만) |
| 속도 | 빠름 (로딩 없음) | 약간 느림 (로딩) |
| 메모리 | 중복 가능 | 공유 가능 |
| 업데이트 | 재링크 필요 | 라이브러리만 교체 |
| 배포 | 간단 (단일 파일) | 복잡 (의존성) |
실전 팁
링킹 전략:
- 개발: 동적 링킹 (빠른 빌드)
- 배포: 정적 링킹 (간단한 배포)
- 공유 라이브러리: 동적 링킹
- 임베디드: 정적 링킹
에러 해결:
undefined reference: 오브젝트/라이브러리 추가cannot find -l:-L경로 추가cannot open shared object: rpath 또는LD_LIBRARY_PATH설정multiple definition: 중복 정의 제거
최적화:
- LTO로 전체 프로그램 최적화
-O3와 함께 사용- 빌드 시간 증가 고려
- 프로덕션 빌드에만 적용
다음 단계
- C++ Name Mangling
- C++ Compilation Process
- C++ Makefile
관련 글
- C++ Compilation Process |
- C++ CRTP 완벽 가이드 | 정적 다형성과 컴파일 타임 최적화
- C++ Dynamic Initialization |
- C++ 정적 초기화 순서 |
- C++ Initialization Order 완벽 가이드 | 초기화 순서의 모든 것