콘텐츠로 이동

C++ 링커와 링킹 과정 완전 이해

C++ 프로그램은 전처리 → 컴파일 → 어셈블 → 링킹 단계를 거쳐 실행 파일이 됩니다. 링킹은 여러 오브젝트 파일(.o/.obj)과 라이브러리를 결합하여 최종 실행 파일을 만드는 단계입니다. 링커 오류(undefined reference, multiple definition 등)는 링킹 과정을 이해해야 해결할 수 있습니다.


// 파일 구조
// math.h, math.cpp, main.cpp
// 컴파일 (각 파일 독립적으로)
g++ -c math.cpp -o math.o // 오브젝트 파일 생성
g++ -c main.cpp -o main.o
// 링킹 (오브젝트 파일 결합)
g++ math.o main.o -o program

각 컴파일 단위(translation unit)는 독립적으로 컴파일되며, 외부 심볼은 링킹 단계에서 해소됩니다.


2.1 정의(Definition)와 선언(Declaration)

섹션 제목: “2.1 정의(Definition)와 선언(Declaration)”
// 선언: 심볼의 존재를 알림 (헤더에 위치)
extern int globalValue; // 변수 선언
void calculate(int a, int b); // 함수 선언
// 정의: 실제 구현 (.cpp에 위치)
int globalValue = 42; // 변수 정의
void calculate(int a, int b) { // 함수 정의
std::cout << a + b << "\n";
}
a.cpp
void foo(); // 선언만, 정의 없음
int main() {
foo(); // 링커: "undefined reference to foo()"
}

해결: foo()의 정의가 있는 파일을 링크에 포함.


C++의 핵심 규칙: 같은 엔티티의 정의는 전체 프로그램에서 하나만 존재해야 합니다.

math.cpp
int add(int a, int b) { return a + b; }
// util.cpp
int add(int a, int b) { return a + b; } // ODR 위반!
// 링커: "multiple definition of add()"

inline 함수는 여러 TU에서 정의될 수 있지만 모든 정의가 동일해야 합니다.

header.h
inline int add(int a, int b) { return a + b; } // 여러 TU에서 포함 가능
// math.h — 이중 포함 방지
#pragma once
// 또는
#ifndef MATH_H
#define MATH_H
void calculate();
#endif

다른 TU에서 참조 가능합니다.

int globalVar = 10; // 외부 링키지
void func() {} // 외부 링키지

현재 TU에서만 접근 가능합니다.

static int localVar = 10; // 내부 링키지
static void localFunc() {} // 내부 링키지
// C++11: 익명 네임스페이스 (권장)
namespace {
int anotherLocal = 20; // 내부 링키지
}
void func() {
int local = 5; // 링키지 없음 — 함수 내부 변수
}

Terminal window
# 정적 라이브러리 생성
ar rcs libmath.a math.o
# 정적 링킹
g++ main.o -L. -lmath -o program
  • 라이브러리 코드가 실행 파일에 포함
  • 배포 시 라이브러리 불필요
  • 실행 파일 크기 증가
Terminal window
# 공유 라이브러리 생성
g++ -shared -fPIC math.cpp -o libmath.so
# 동적 링킹
g++ main.o -L. -lmath -o program
# 런타임에 libmath.so 필요
  • 라이브러리 코드를 메모리에서 공유
  • 실행 파일 크기 감소
  • 라이브러리 업데이트가 모든 프로그램에 적용

C++ 함수는 오버로딩 지원을 위해 이름을 맹글링(mangling)합니다. C 코드와 인터페이스하려면 extern "C"로 맹글링을 억제합니다.

// C++에서 C 함수 사용
extern "C" {
int c_function(int a, int b); // C 이름 그대로 사용
}
// C에서 C++ 함수 노출
extern "C" int my_cpp_function(int x) {
return x * 2;
}
// 헤더에서 C/C++ 공용
#ifdef __cplusplus
extern "C" {
#endif
void shared_api();
#ifdef __cplusplus
}
#endif
// C++ 함수: void foo(int, double)
// 맹글링 후: _Z3foodd (GCC), ?foo@@YAXNH@Z (MSVC)
// extern "C": 맹글링 없이 foo 그대로

LTO는 링킹 단계에서 여러 TU에 걸친 최적화를 수행합니다.

Terminal window
# GCC LTO 활성화
g++ -O2 -flto -c a.cpp -o a.o
g++ -O2 -flto -c b.cpp -o b.o
g++ -O2 -flto a.o b.o -o program

LTO가 가능한 최적화:

  • TU 경계를 넘는 인라인 확장
  • 불필요한 심볼 제거 (dead code elimination)
  • 크로스 TU 상수 전파

/usr/bin/ld: main.o: undefined reference to `Database::connect()'

원인: Database 클래스 구현 파일을 링크하지 않음
해결: g++ main.o database.o -o program 또는 -ldatabase

/usr/bin/ld: math.o: multiple definition of `add(int, int)'

원인: ODR 위반 — 같은 함수가 두 파일에서 정의됨
해결: 헤더에 있는 정의를 inline으로 만들거나 하나의 .cpp로 이동

// A가 B에 의존, B가 A에 의존 → 링크 순서 문제
g++ a.o b.o // 오류 가능
g++ a.o b.o a.o // 순환 참조 해소
# 또는
g++ -Wl,--start-group a.o b.o -Wl,--end-group

// GCC/Clang: 심볼 가시성 제어
__attribute__((visibility("default"))) void publicFunc() {} // 외부 노출
__attribute__((visibility("hidden"))) void privateFunc() {} // 외부 숨김
// CMake에서
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN YES)

개념설명
링킹오브젝트 파일 결합, 심볼 해소
ODR전체 프로그램에서 정의는 하나
내부 링키지static, 익명 네임스페이스
extern “C”C++ 맹글링 억제
정적 링킹라이브러리 코드를 실행 파일에 포함
동적 링킹런타임에 공유 라이브러리 로드
LTO링킹 단계 크로스 TU 최적화