C++ CMake 링크 에러 LNK2019 | 원인과 해결 [#49-2]
이 글의 핵심
C++ CMake 링크 에러 LNK2019에 대한 실전 가이드입니다. 원인과 해결 [#49-2] 등을 예제와 함께 설명합니다.
들어가며: “undefined reference to `…’” / LNK2019가 나올 때
검색해서 들어오는 분들을 위해
undefined reference to `함수명’ (Unix/Linux) 또는 LNK2019: unresolved external symbol (MSVC)는 링크 단계에서 “선언은 있는데 정의를 찾을 수 없다”는 뜻입니다. 컴파일은 되고 링크에서 실패하는 대표적인 경우를 정리하고, CMake 기준으로 어디를 고치면 되는지 해결책을 제시합니다.
이 글에서 다루는 것:
- 정의 누락: 선언만 있고 구현이 없음, 다른 번역 단위(하나의 .cpp가 컴파일된 결과)에만 있음
- 라이브러리 링크 누락: CMake에서 target_link_libraries 에 빠진 라이브러리
- 라이브러리 not found: find_package 실패, 경로 오류
- C/C++ 링킹: C로 컴파일된 라이브러리를 C++에서 쓸 때 extern “C”
- 순서·중복: 링크 순서, 같은 심볼이 여러 번 정의된 경우 (multiple definition)
- 디버깅 단계: 에러 메시지 해석부터 해결까지
- 프로덕션 패턴: 재발 방지, CI/CD 연동
관련 글: CMake 입문, 컴파일 과정, CMake 고급.
컴파일 vs 링크: 왜 “컴파일은 되는데” 링크에서만 실패할까?
C++ 빌드는 컴파일과 링크 두 단계로 나뉩니다.
flowchart LR
subgraph Compile["컴파일 단계"]
A[.cpp 소스] --> B[컴파일러]
B --> C[.o / .obj 오브젝트]
end
subgraph Link["링크 단계"]
C --> D[링커]
E[.a / .so / .lib] --> D
D --> F[실행 파일 / DLL]
end
- 컴파일: 각 .cpp를 독립적으로 처리. 선언만 있으면 “이 함수가 어딘가에 있다”고 믿고 통과. 정의는 이 단계에서 필요 없음.
- 링크: 모든 오브젝트와 라이브러리를 합쳐 하나의 실행 파일을 만듦. 이때 정의를 실제로 찾음. 못 찾으면 undefined reference / LNK2019.
그래서 “헤더만 include하고 라이브러리를 링크 안 했을 때” 컴파일은 되고 링크에서만 실패합니다.
문제 시나리오: “이런 상황에서 막혔다”
시나리오 1: 새 라이브러리 추가 후 undefined reference
"Boost.Asio를 추가했는데 undefined reference to `boost::asio::...' 가 나와요."
"컴파일은 되는데 링크에서만 실패해요."
상황: 헤더만 include하고 target_link_libraries에 Boost를 넣지 않았거나, 컴포넌트(Boost::system 등)를 빠뜨린 경우입니다.
원인: find_package(Boost REQUIRED) 후 target_link_libraries에 Boost 타겟을 연결하지 않음.
해결 포인트: target_link_libraries(my_target PRIVATE Boost::asio Boost::system) 등 필요한 컴포넌트를 명시.
시나리오 2: “multiple definition of `foo’” 에러
"헤더에 함수를 정의했더니 multiple definition 에러가 나요."
"여러 .cpp에서 같은 함수가 중복 정의됐대요."
상황: 헤더에 일반 함수(인라인/template 아님)를 정의하고, 여러 .cpp에서 include하면 각 번역 단위마다 정의가 복사되어 링크 시 중복 정의 에러가 납니다.
원인: ODR(One Definition Rule) 위반. 헤더에는 선언만, 정의는 한 .cpp에만.
해결 포인트: inline, static, 또는 anonymous namespace로 번역 단위 한정, 또는 정의를 .cpp로 이동.
시나리오 3: “cannot find -lfoo” / “library not found”
"vcpkg로 설치했는데 CMake가 libfoo를 못 찾아요."
"Could not find a package configuration file provided by 'Foo' 에러가 나요."
상황: 라이브러리는 설치했지만 CMake가 경로를 모르거나, CMAKE_TOOLCHAIN_FILE을 지정하지 않은 경우입니다.
원인: find_package가 vcpkg/Conan 경로를 사용하지 않음. CMAKE_PREFIX_PATH 또는 CMAKE_TOOLCHAIN_FILE 미설정.
해결 포인트: cmake -DCMAKE_TOOLCHAIN_FILE=[vcpkg]/scripts/buildsystems/vcpkg.cmake .. 등 툴체인 지정.
시나리오 4: C 라이브러리를 C++에서 호출할 때
"libcurl을 C++에서 쓰는데 undefined reference to `curl_easy_init' 가 나와요."
"C로 빌드된 .a 파일을 링크했는데 심볼을 못 찾아요."
상황: C 라이브러리는 이름 맹글링이 없어 curl_easy_init 그대로 export되는데, C++은 _Z12curl_easy_initv 같은 맹글된 이름으로 찾습니다.
원인: C++ 맹글링 vs C 심볼 불일치. extern "C" 선언 누락.
해결 포인트: C 라이브러리 헤더를 extern "C" { ... }로 감싸기.
시나리오 5: 서브디렉터리 라이브러리 링크 누락
"add_subdirectory로 mylib를 추가했는데 app에서 undefined reference to `mylib::init()' 가 나와요."
"mylib.cpp는 분명히 있는데요."
상황: add_library(mylib STATIC mylib.cpp)로 라이브러리는 만들었지만, 실행 파일에서 target_link_libraries(app PRIVATE mylib)를 하지 않은 경우입니다.
원인: 라이브러리 타겟을 링크하지 않음. add_executable에 소스만 넣고 target_link_libraries를 빼먹음.
해결 포인트: target_link_libraries(app PRIVATE mylib) 추가.
시나리오별 해결 방향 요약
| 시나리오 | 특징 | 권장 접근 |
|---|---|---|
| 외부 라이브러리 undefined | find_package 후 링크 누락 | target_link_libraries에 타겟 추가 |
| multiple definition | 헤더에 정의, 여러 .cpp에서 include | inline/static/.cpp로 이동 |
| library not found | 패키지 설치했는데 못 찾음 | CMAKE_TOOLCHAIN_FILE, CMAKE_PREFIX_PATH |
| C 라이브러리 unresolved | C++에서 C 함수 호출 | extern “C” 선언 |
| 서브디렉터리 라이브러리 | add_library만 하고 링크 안 함 | target_link_libraries 추가 |
링크 에러 디버깅 흐름
flowchart TB
subgraph Detect["링크 에러 발생"]
A[undefined reference / LNK2019 / multiple definition] --> B{에러 유형}
end
subgraph Undef["undefined reference"]
B -->|정의 못 찾음| C[심볼명 확인]
C --> D{자체 코드?}
D -->|예| E[구현 .cpp가 add_executable/add_library에 포함?]
D -->|아니오| F[target_link_libraries에 라이브러리 추가]
E -->|아니오| G[target_sources 또는 target_link_libraries 수정]
end
subgraph MultiDef["multiple definition"]
B -->|중복 정의| H[헤더에 일반 함수 정의?]
H -->|예| I[inline/static/.cpp로 이동]
end
subgraph NotFound["library not found"]
B -->|라이브러리 못 찾음| J[CMAKE_TOOLCHAIN_FILE 확인]
J --> K[find_package 경로 확인]
end
개념을 잡는 비유
이 글의 주제는 여러 부품이 맞물리는 시스템으로 보시면 이해가 빠릅니다. 한 레이어(저장·네트워크·관측)의 선택이 옆 레이어에도 영향을 주므로, 본문에서는 트레이드오프를 숫자와 패턴으로 정리합니다.
목차
- 에러 메시지 읽기
- 원인 1: 정의 누락·다른 파일에만 구현
- 원인 2: 라이브러리 링크 누락 (CMake)
- 원인 3: 라이브러리 not found
- 원인 4: C/C++ 혼합·extern “C”
- 원인 5: 링크 순서·중복 정의
- 완전한 CMake 링크 에러 예제
- 디버깅 단계
- 자주 발생하는 에러와 해결법
- 프로덕션 패턴
- 자주 묻는 질문 (FAQ)
1. 에러 메시지 읽기
Unix (GCC/Clang)
- undefined reference to
함수명또는 undefined reference to네임스페이스::클래스::메서드 - 어떤 심볼이 정의를 찾지 못했는지가 그대로 나옵니다. 함수명을 복사해 “선언은 어디 있는지”, “구현은 어디 있어야 하는지”를 찾습니다.
MSVC
- LNK2019: unresolved external symbol “함수 시그니처”
- 시그니처가 데코레이션(이름 맹글링)된 형태로 나오므로, “선언과 정의의 시그니처가 일치하는지”(const, 참조, 네임스페이스 등)를 확인합니다.
2. 원인 1: 정의 누락·다른 파일에만 구현
상황
- 헤더에는 함수·클래스 선언이 있는데, 구현(.cpp) 이 없거나, 빌드에 포함되지 않은 경우입니다.
- 선언만 있는 함수를 호출하면 컴파일은 통과(선언이 있으므로)하고, 링크 시 “정의를 못 찾음”이 됩니다.
해결
- 구현을 해당 .cpp에 추가하고, 그 .cpp가 add_executable 또는 add_library 에 들어가 있는지 확인합니다.
- CMake: target_sources(my_target PRIVATE my_impl.cpp)로 my_impl.cpp가 my_target의 소스 목록에 들어가야 링크 시 해당 오브젝트가 포함됩니다. “선언은 헤더에 있는데 구현은 다른 .cpp에 있다”면 그 .cpp가 반드시 같은 실행 파일/라이브러리 타겟의 소스에 들어가 있어야 하고, 서브디렉터리에서 add_library로 만든 라이브러리는 target_link_libraries(실행파일 PRIVATE 그_라이브러리)로 링크해야 “undefined reference”가 사라집니다. 실무에서는 add_executable에 파일을 빼먹었을 때 이 에러가 가장 자주 납니다.
3. 원인 2: 라이브러리 링크 누락 (CMake)
상황
- 외부 라이브러리(Boost, OpenSSL, pthread 등)의 헤더만 include하고, 실제 라이브러리 파일(.a, .so, .lib) 을 링크하지 않은 경우입니다.
- 예: pthread 함수를 쓰는데 -lpthread 를 안 넣은 경우.
해결 (CMake)
- find_package 로 찾은 타겟을 target_link_libraries 에 넣습니다.
- find_package(Threads REQUIRED) → target_link_libraries(my_target PRIVATE Threads::Threads)
- find_package(Boost REQUIRED) → target_link_libraries(my_target PRIVATE Boost::boost) 등 (컴포넌트가 있으면 Boost::system 등)
- 직접 경로를 쓸 때: target_link_libraries(my_target PRIVATE /path/to/libfoo.a) 또는 link_directories 후 foo 로 링크. 가능하면 find_package + IMPORTED 타겟을 쓰는 편이 유지보수에 좋습니다.
- 실행 파일뿐 아니라 그 실행 파일이 링크하는 라이브러리가 또 다른 라이브러리에 의존하면, 그 라이브러리에도 target_link_libraries 로 의존성을 넣어 줍니다. 그래야 “실행 파일 → A → B”일 때 B도 링크됩니다.
4. 원인 3: 라이브러리 not found
상황
find_package(Foo REQUIRED)실패: Could not find a package configuration file provided by “Foo”- 링커 단계: cannot find -lfoo
해결
vcpkg 사용 시:
# vcpkg로 패키지 설치
vcpkg install fmt:x64-linux
# CMake 설정 시 툴체인 필수
cmake -B build -DCMAKE_TOOLCHAIN_FILE=[vcpkg root]/scripts/buildsystems/vcpkg.cmake
# CMakeLists.txt
find_package(fmt REQUIRED)
target_link_libraries(my_app PRIVATE fmt::fmt)
주의사항: find_package만 하고 target_link_libraries를 빼면 헤더는 보여도 링크 단계에서 undefined reference가 납니다.
CMAKE_PREFIX_PATH 사용:
cmake -B build -DCMAKE_PREFIX_PATH=/usr/local
직접 경로 지정:
set(FOO_ROOT "/opt/foo")
find_library(FOO_LIBRARY foo HINTS ${FOO_ROOT}/lib)
find_path(FOO_INCLUDE_DIR foo.h HINTS ${FOO_ROOT}/include)
target_link_libraries(my_app PRIVATE ${FOO_LIBRARY})
target_include_directories(my_app PRIVATE ${FOO_INCLUDE_DIR})
5. 원인 4: C/C++ 혼합·extern “C”
상황
- C로 작성·컴파일된 라이브러리의 함수를 C++에서 부르는 경우. C++은 이름 맹글링을 하므로 “함수명만”으로는 C 심볼을 찾지 못합니다.
- undefined reference to `foo’ 인데, 라이브러리에는 foo 가 C 스타일로만 export되어 있는 경우입니다.
해결
- C++ 쪽 헤더에서 C 라이브러리 함수 선언을 extern “C” 로 감쌉니다.
extern "C" { void foo(int x); } - 해당 헤더를 C와 C++ 양쪽에서 include할 수 있게 하려면:
#ifdef __cplusplus extern "C" { #endif void foo(int x); #ifdef __cplusplus } #endif - CMake에서 C 라이브러리를 쓸 때는 find_library 등으로 찾은 뒤 target_link_libraries 에 넣고, 헤더에서 extern “C” 만 확실히 하면 됩니다.
6. 원인 5: 링크 순서·중복 정의
링크 순서
- 일부 링커는 앞에 나온 오브젝트/라이브러리에서 나중에 나온 심볼을 참조할 때, “아직 안 본” 라이브러리를 다시 찾지 않습니다. 그래서 의존되는 쪽이 의존하는 쪽보다 뒤에 와야 할 수 있습니다.
- CMake에서는 target_link_libraries(A PRIVATE B C) 로 두면 보통 B, C 순서로 링크되고, B가 C에 의존하면 target_link_libraries(B PRIVATE C) 로 B에 C를 넣어 두면 A를 링크할 때 B와 C가 적절히 포함됩니다. PUBLIC/PRIVATE 으로 전파되는 의존성을 두면 순서 문제를 줄일 수 있습니다.
중복 정의 (multiple definition)
- 같은 심볼이 여러 오브젝트/라이브러리에 정의되어 있으면 multiple definition 에러가 납니다. inline·static·anonymous namespace 로 정의를 번역 단위 안에 묶거나, 정의를 한 .cpp에만 두고 나머지는 선언만 하도록 정리합니다.
- 헤더에 일반 함수 정의를 두면 include하는 모든 .cpp에 정의가 복사되므로, inline 이나 template 이 아니면 헤더에 두지 않습니다.
7. 완전한 CMake 링크 에러 예제
예제 1: undefined reference — 구현 파일 누락
에러 메시지:
/usr/bin/ld: main.cpp.o: in function `main':
main.cpp:(.text+0x15): undefined reference to `utils::compute(int)'
collect2: error: ld returned 1 exit status
잘못된 CMakeLists.txt:
cmake_minimum_required(VERSION 3.14)
project(Demo LANGUAGES CXX)
add_executable(app main.cpp)
# utils.cpp를 빼먹음!
main.cpp:
#include "utils.h"
int main() {
utils::compute(42);
return 0;
}
utils.h:
#pragma once
namespace utils {
int compute(int x);
}
utils.cpp (존재하지만 빌드에 미포함):
#include "utils.h"
namespace utils {
int compute(int x) { return x * 2; }
}
올바른 CMakeLists.txt:
cmake_minimum_required(VERSION 3.14)
project(Demo LANGUAGES CXX)
add_executable(app main.cpp utils.cpp)
# 또는 target_sources(app PRIVATE utils.cpp)
예제 2: undefined reference — 라이브러리 링크 누락
에러 메시지:
undefined reference to `boost::system::generic_category()'
undefined reference to `boost::asio::io_context::io_context()'
잘못된 CMakeLists.txt:
cmake_minimum_required(VERSION 3.14)
project(AsioDemo LANGUAGES CXX)
find_package(Boost REQUIRED)
# target_link_libraries 누락!
add_executable(app main.cpp)
target_include_directories(app PRIVATE ${Boost_INCLUDE_DIRS})
올바른 CMakeLists.txt:
cmake_minimum_required(VERSION 3.14)
project(AsioDemo LANGUAGES CXX)
find_package(Boost REQUIRED COMPONENTS system)
find_package(asio REQUIRED)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE Boost::system asio::asio)
target_include_directories(app PRIVATE ${Boost_INCLUDE_DIRS})
예제 3: multiple definition — 헤더에 정의
에러 메시지:
multiple definition of `helper::add(int, int)'
/usr/bin/ld: a.cpp.o: in function `helper::add(int, int)'
/usr/bin/ld: b.cpp.o: in function `helper::add(int, int)'
잘못된 helper.h:
#pragma once
namespace helper {
int add(int a, int b) { // ❌ 헤더에 정의 → include하는 모든 .cpp에 복사
return a + b;
}
}
해결 1: inline 사용:
#pragma once
namespace helper {
inline int add(int a, int b) { // ✅ inline: ODR 허용
return a + b;
}
}
해결 2: 선언/정의 분리:
// helper.h
#pragma once
namespace helper {
int add(int a, int b); // 선언만
}
// helper.cpp
#include "helper.h"
namespace helper {
int add(int a, int b) { return a + b; } // 정의는 한 곳에만
}
add_library(helper STATIC helper.cpp)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE helper)
예제 4: library not found — vcpkg 툴체인 미지정
에러 메시지:
CMake Error at CMakeLists.txt:10 (find_package):
Could not find a package configuration file provided by "fmt" with any of
the following names:
fmtConfig.cmake
fmt-config.cmake
원인: vcpkg로 fmt를 설치했지만 CMake가 vcpkg 경로를 모름.
해결:
vcpkg install fmt:x64-linux
cmake -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
cmake --build build
# CMakeLists.txt
find_package(fmt REQUIRED)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE fmt::fmt)
예제 5: C 라이브러리 — extern “C” 누락
에러 메시지:
undefined reference to `sqlite3_open'
잘못된 코드 (C++에서 C 함수 호출):
// main.cpp
#include <sqlite3.h> // C 헤더인데 C++에서 include 시 맹글링 적용
int main() {
sqlite3* db = nullptr;
sqlite3_open("test.db", &db); // C++는 sqlite3_open을 다른 이름으로 찾음
return 0;
}
sqlite3.h (C 라이브러리 표준 패턴):
#ifdef __cplusplus
extern "C" {
#endif
int sqlite3_open(const char *filename, sqlite3 **ppDb);
#ifdef __cplusplus
}
#endif
- 대부분의 C 라이브러리 헤더는 이미 위와 같이
extern "C"를 포함합니다. 만약 포함되지 않은 구형 라이브러리라면, C++ 쪽에서 래퍼 헤더를 만들어 감싸면 됩니다.
CMakeLists.txt:
find_package(PkgConfig REQUIRED)
pkg_check_modules(SQLITE3 REQUIRED sqlite3)
add_executable(app main.cpp)
target_include_directories(app PRIVATE ${SQLITE3_INCLUDE_DIRS})
target_link_libraries(app PRIVATE ${SQLITE3_LIBRARIES})
예제 6: OpenSSL — 여러 컴포넌트 링크
에러 메시지:
undefined reference to `SSL_CTX_new'
undefined reference to `SSL_library_init'
원인: OpenSSL은 libssl과 libcrypto 두 라이브러리로 나뉘며, 둘 다 링크해야 합니다.
해결:
find_package(OpenSSL REQUIRED)
add_executable(app main.cpp)
target_include_directories(app PRIVATE ${OPENSSL_INCLUDE_DIR})
target_link_libraries(app PRIVATE OpenSSL::SSL OpenSSL::Crypto)
예제 7: nlohmann/json — 헤더 전용인데 링크 에러
상황: nlohmann/json은 헤더 전용이라 링크할 라이브러리가 없습니다. 그런데 undefined reference가 난다면?
원인: 보통 다른 라이브러리 문제입니다. nlohmann/json을 쓰는 다른 코드(예: REST 클라이언트)가 내부적으로 libcurl 등을 쓰는데, 그쪽 링크를 안 한 경우입니다. 또는 FetchContent로 가져온 json이 fmt 등에 의존하는데, 그 의존성을 링크하지 않은 경우입니다.
해결: nlohmann/json 자체는 target_link_libraries(app PRIVATE nlohmann_json::nlohmann_json)만 하면 됩니다(INTERFACE 라이브러리). 에러가 난다면 실제로 unresolved인 심볼이 어느 라이브러리 것인지 nm 등으로 확인하세요.
예제 8: 링크 순서 문제 — 의존하는 쪽이 뒤에 와야 함
에러 메시지 (일부 링커에서):
undefined reference to `bar' # libfoo가 libbar를 사용하는데, bar가 foo 뒤에 와야 함
잘못된 순서:
target_link_libraries(app PRIVATE foo bar) # foo가 bar의 심볼을 쓰면, bar가 뒤에 와야 함
올바른 순서 (의존되는 쪽이 뒤):
target_link_libraries(app PRIVATE foo bar) # foo → bar 의존이면 이 순서가 맞음
# 또는 foo에 bar를 직접 링크
target_link_libraries(foo PRIVATE bar)
target_link_libraries(app PRIVATE foo) # foo를 링크하면 bar도 자동 포함
- CMake의
target_link_libraries는 의존성을 전파하므로, 타겟 간 의존 관계를 올바르게 두면 순서 문제를 피할 수 있습니다.
8. 디버깅 단계
1단계: 에러 메시지에서 심볼명 추출
- undefined reference to
X→X가 누락된 심볼. - LNK2019 → MSVC 출력에서
"..."안의 시그니처 확인.
2단계: 심볼 소유처 파악
- 자체 코드:
grep -r "함수명" src/또는 IDE 검색으로 선언/정의 위치 확인. - 외부 라이브러리: 해당 라이브러리 문서에서 링크 방법 확인.
3단계: 자체 코드인 경우
# 구현 .cpp가 타겟에 포함되는지 확인
grep -r "my_impl.cpp" CMakeLists.txt
grep -r "target_sources\|add_executable\|add_library" CMakeLists.txt
add_executable/add_library/target_sources에 해당 .cpp가 있는지 확인.- 라이브러리로 분리했다면
target_link_libraries(실행파일 PRIVATE 그_라이브러리)확인.
4단계: 외부 라이브러리인 경우
# 라이브러리 내 심볼 확인 (Unix)
nm -C /path/to/libfoo.a | grep "함수명"
nm -D /path/to/libfoo.so | grep "함수명"
# MSVC
dumpbin /SYMBOLS libfoo.lib | findstr "함수명"
- 심볼이 있으면: 링크 순서,
target_link_libraries누락 확인. - 심볼이 없으면: 잘못된 라이브러리 버전, C/C++ 혼합 시
extern "C"확인.
5단계: CMake 링크 그래프 확인
cmake --graphviz=graph.dot ..
dot -Tpng graph.dot -o graph.png
- 실행 파일 → 라이브러리 의존 관계가 올바른지 시각적으로 확인.
6단계: 상세 링커 출력
# GCC/Clang: 링크 시 사용되는 라이브러리 확인
cmake -B build -DCMAKE_EXE_LINKER_FLAGS="-Wl,--verbose" ..
cmake --build build 2>&1 | less
9. 자주 발생하는 에러와 해결법
에러 1: “undefined reference to `pthread_create’”
원인: pthread를 사용하는데 -lpthread 미링크.
해결:
find_package(Threads REQUIRED)
target_link_libraries(my_app PRIVATE Threads::Threads)
에러 2: “undefined reference to `dlopen’”
원인: 동적 로딩(dlopen) 사용 시 -ldl 미링크.
해결:
target_link_libraries(my_app PRIVATE ${CMAKE_DL_LIBS})
# 또는
find_library(DL_LIBRARY dl)
target_link_libraries(my_app PRIVATE ${DL_LIBRARY})
에러 3: Windows에서 “unresolved external symbol _main”
원인: 콘솔 앱인데 WinMain을 찾거나, 그 반대. 또는 /SUBSYSTEM:CONSOLE vs WINDOWS 불일치.
해결: add_executable에 올바른 main이 있는지, Windows GUI 앱이면 WIN32 옵션 사용.
add_executable(console_app main.cpp) # 콘솔
add_executable(gui_app WIN32 winmain.cpp) # GUI
에러 4: “undefined reference to vtable for MyClass”
원인: 가상 함수 선언만 있고 정의가 없음. 또는 해당 .cpp가 빌드에 미포함.
해결: 순수 가상 함수가 아닌 모든 가상 함수에 정의를 제공하고, 해당 .cpp를 타겟에 포함.
에러 5: “cannot find -lfoo” (library not found)
원인: libfoo.a / libfoo.so 경로를 링커가 모름.
해결:
link_directories(/path/to/lib)
target_link_libraries(my_app PRIVATE foo)
# 또는
target_link_libraries(my_app PRIVATE /path/to/lib/libfoo.a)
- 가능하면
find_package또는find_library로 경로를 찾아 타겟에 연결하는 것이 좋습니다.
에러 6: “multiple definition” — static 변수
원인: 헤더에 static int counter;를 두고 여러 .cpp에서 include하면, 각 번역 단위마다 별도 counter가 생깁니다. “multiple definition”이 나는 경우는 보통 non-inline 함수 정의입니다. static 함수/변수는 번역 단위 한정이므로 링크 에러는 안 나고, inline이 아닌 함수 정의가 원인입니다.
해결: 함수 정의는 .cpp에 두거나 inline으로. 변수는 inline(C++17) 또는 한 .cpp에만 정의.
에러 7: “undefined reference to `std::__throw_bad_alloc’”
원인: 표준 라이브러리 예외 심볼. -static 링크 시 libstdc++가 제대로 링크되지 않았거나, -nostdlib 사용 시 발생.
해결:
# -nostdlib 사용 시 수동으로 링크
target_link_libraries(my_app PRIVATE stdc++)
에러 8: “undefined reference to `__atomic_load_8’”
원인: 64비트 원자 연산을 쓰는데 libatomic이 링크되지 않음. 일부 32비트 타겟에서 자주 발생.
해결:
find_library(ATOMIC_LIBRARY atomic)
if(ATOMIC_LIBRARY)
target_link_libraries(my_app PRIVATE ${ATOMIC_LIBRARY})
endif()
에러 9: macOS “undefined reference to `_pthread_mutex_lock’”
원인: macOS에서는 pthread가 시스템 라이브러리에 포함되어 있어 별도 -lpthread가 필요 없지만, 일부 크로스 컴파일이나 특수 설정에서 누락될 수 있음.
해결:
find_package(Threads REQUIRED)
target_link_libraries(my_app PRIVATE Threads::Threads)
에러 10: “undefined reference to `__gxx_personality_v0’”
원인: C++ 예외 처리 런타임. C 링커를 사용했거나, C++ 런타임이 링크되지 않은 경우.
해결: C++ 프로젝트임을 CMake에 명시. project(MyProj LANGUAGES CXX) 또는 enable_language(CXX).
10. 프로덕션 패턴
패턴 1: INTERFACE/IMPORTED 라이브러리로 의존성 캡슐화
# FindMyLib.cmake 또는 Config 파일
add_library(MyLib::MyLib SHARED IMPORTED)
set_target_properties(MyLib::MyLib PROPERTIES
IMPORTED_LOCATION "${MYLIB_ROOT}/lib/libmylib.so"
INTERFACE_INCLUDE_DIRECTORIES "${MYLIB_ROOT}/include"
)
- 프로젝트에서는
target_link_libraries(app PRIVATE MyLib::MyLib)만 하면 include 경로와 링크가 자동 전파됩니다.
패턴 2: FetchContent로 버전 고정
include(FetchContent)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.1.1
)
FetchContent_MakeAvailable(fmt)
target_link_libraries(my_app PRIVATE fmt::fmt)
- 외부 의존성 버전을 소스와 함께 고정하여 재현 가능한 빌드를 만듭니다.
패턴 3: CI에서 vcpkg/Conan 툴체인 일관 적용
# GitHub Actions 예시
- name: Configure CMake
run: |
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake \
-DCMAKE_BUILD_TYPE=Release
- 로컬과 CI가 동일한 툴체인을 사용하도록 하여 “로컬에선 되는데 CI에서 안 돼요”를 방지합니다.
패턴 4: 링크 에러 재발 방지 체크리스트
- 새 라이브러리 추가 시
target_link_libraries에 반드시 추가 - 헤더에는 선언만, 정의는 .cpp에 (inline/template 제외)
- C 라이브러리 사용 시
extern "C"확인 - vcpkg/Conan 사용 시
CMAKE_TOOLCHAIN_FILE문서화 -
add_subdirectory로 추가한 라이브러리는target_link_libraries로 링크
패턴 5: 의존성 전파 (PUBLIC/PRIVATE/INTERFACE)
add_library(mylib STATIC mylib.cpp)
target_include_directories(mylib PUBLIC include/) # 링크하는 쪽에 전파
target_link_libraries(mylib PRIVATE Boost::system) # mylib만 사용
add_executable(app main.cpp)
target_link_libraries(app PRIVATE mylib) # mylib의 PUBLIC include 자동 적용
PUBLIC: 이 타겟을 링크하는 쪽에도 전파.PRIVATE: 이 타겟만 사용.INTERFACE: 헤더 전용 라이브러리.
실무에서 자주 하는 실수
실수 1: 새 .cpp 추가 후 CMakeLists.txt 업데이트 안 함
- 증상:
undefined reference to 새로_만든_함수형태 - 예방: 새 소스 파일 추가 시
add_executable또는target_sources에 반드시 추가. 일부 IDE는 자동으로 추가하지만, 수동 빌드 시 누락되기 쉽습니다.
실수 2: find_package만 하고 target_link_libraries 빼먹음
- 증상: Boost, OpenSSL 등 외부 라이브러리 함수에서 undefined reference
- 예방:
find_package(X REQUIRED)다음 줄에target_link_libraries(my_target PRIVATE X::X)등 반드시 작성.
실수 3: vcpkg/Conan 사용 시 CMAKE_TOOLCHAIN_FILE 미전달
- 증상:
Could not find a package configuration file provided by "fmt"에러 - 예방: README에
cmake -DCMAKE_TOOLCHAIN_FILE=...명령어를 명시. CI 스크립트에도 동일하게 적용.
실수 4: 헤더에 구현을 넣고 여러 .cpp에서 include
- 증상:
multiple definition of 함수명형태 - 예방: 헤더에는 선언만. 구현은 한 .cpp에만. 인라인 함수·템플릿은 예외.
실수 5: add_subdirectory로 라이브러리 추가 후 링크 안 함
- 증상:
undefined reference to mylib::함수형태 - 예방:
add_subdirectory(lib)후target_link_libraries(app PRIVATE mylib)필수.
빠른 참조: 에러 메시지 → 해결 키워드
| 에러 메시지 패턴 | 첫 번째 확인 사항 |
|---|---|
undefined reference to 내_네임스페이스:: | 해당 .cpp가 add_executable/add_library에 포함? |
undefined reference to boost:: | target_link_libraries에 Boost::* 추가 |
undefined reference to pthread_ | Threads::Threads 링크 |
undefined reference to curl_ | libcurl 링크, extern “C” 확인 |
undefined reference to SSL_ | OpenSSL::SSL, OpenSSL::Crypto 링크 |
multiple definition of | 헤더에 일반 함수 정의? → .cpp로 이동 또는 inline |
cannot find -lfoo | link_directories, find_library, CMAKE_TOOLCHAIN_FILE |
Could not find ... Foo | CMAKE_TOOLCHAIN_FILE, CMAKE_PREFIX_PATH |
undefined reference to vtable | 가상 함수 정의 누락, 해당 .cpp 포함 여부 |
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Segmentation fault | core dump
- C++ CMake 고급 | 멀티 타겟·외부 라이브러리 관리 (대규모 프로젝트 빌드)
- CMake 입문 | 수십 개 파일 컴파일할 때 필요한 빌드 자동화 (CMakeLists.txt 기초)
이 글에서 다루는 키워드 (관련 검색어)
CMake 링크 에러, undefined reference 등으로 검색하시면 이 글이 도움이 됩니다.
정리
| 증상 | 의심 원인 | 해결 |
|---|---|---|
| undefined reference to `foo’ | foo 정의 없음 / 다른 번역 단위에만 있음 | 구현 추가, CMake에 소스/라이브러리 포함 |
| 외부 라이브러리 함수만 unresolved | 해당 라이브러리 미링크 | target_link_libraries 에 추가 |
| cannot find -lfoo | 라이브러리 경로 미설정 | CMAKE_TOOLCHAIN_FILE, link_directories, find_library |
| C 라이브러리 함수가 unresolved | C++ 맹글링 | extern “C” 로 선언 |
| multiple definition | 같은 심볼이 여러 곳에 정의 | inline/static/한 곳에만 정의 |
실전 체크리스트
실무에서 이 개념을 적용할 때 확인해야 할 사항입니다.
코드 작성 전
- 이 기법이 현재 문제를 해결하는 최선의 방법인가?
- 팀원들이 이 코드를 이해하고 유지보수할 수 있는가?
- 성능 요구사항을 만족하는가?
코드 작성 중
- 컴파일러 경고를 모두 해결했는가?
- 엣지 케이스를 고려했는가?
- 에러 처리가 적절한가?
코드 리뷰 시
- 코드의 의도가 명확한가?
- 테스트 케이스가 충분한가?
- 문서화가 되어 있는가?
이 체크리스트를 활용하여 실수를 줄이고 코드 품질을 높이세요.
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. LNK2019, undefined reference to 등 링크 에러의 흔한 원인과 CMake·빌드 설정에서의 해결 방법을 다룹니다. 새 라이브러리 추가, 멀티 모듈 프로젝트 구성, CI/CD 빌드 실패 시 위 본문의 예제와 디버깅 단계를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다. CMake 입문과 컴파일 과정을 먼저 읽으면 링크 단계를 이해하는 데 도움이 됩니다.
Q. 더 깊이 공부하려면?
A. cppreference, CMake 공식 문서, GNU ld 문서를 참고하세요. nm, objdump, dumpbin으로 오브젝트/라이브러리 심볼을 직접 확인하는 연습을 하면 링크 에러 해결 속도가 빨라집니다.
한 줄 요약: LNK2019·undefined reference 원인과 해결 흐름을 익히면 빌드 에러 대응이 빨라집니다. 다음으로 Asio 데드락 디버깅(#49-3)를 읽어보면 좋습니다.
다음 글: [에러 해결·트러블슈팅 #49-3] Asio 비동기 콜백에서 발생하는 은밀한 데드락(Deadlock) 실전 디버깅
이전 글: [에러 해결·트러블슈팅 #49-1] “Segmentation fault (core dumped)” 완벽 추적 및 디버깅 프로세스
관련 글
- C++ DB 엔진 기초 완벽 가이드 | 저장 엔진·쿼리 파서·실행기·트랜잭션 실전 [#49-1]
- C++ Segmentation fault | core dump
- C++ Asio 데드락 디버깅 | 비동기 콜백 실전 [#49-3]
- C++ 쿼리 최적화 완벽 가이드 | 인덱스 선택·실행 계획·통계·비용 모델·프로덕션 패턴 [#49-3]
- C++ 게임 엔진 기초 | 게임 루프·ECS·씬 그래프·입력 처리 완전 가이드