C++ CMake 링크 에러 LNK2019 | 원인과 해결 [#49-2]

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) 추가.

시나리오별 해결 방향 요약

시나리오특징권장 접근
외부 라이브러리 undefinedfind_package 후 링크 누락target_link_libraries에 타겟 추가
multiple definition헤더에 정의, 여러 .cpp에서 includeinline/static/.cpp로 이동
library not found패키지 설치했는데 못 찾음CMAKE_TOOLCHAIN_FILE, CMAKE_PREFIX_PATH
C 라이브러리 unresolvedC++에서 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. 원인 1: 정의 누락·다른 파일에만 구현
  3. 원인 2: 라이브러리 링크 누락 (CMake)
  4. 원인 3: 라이브러리 not found
  5. 원인 4: C/C++ 혼합·extern “C”
  6. 원인 5: 링크 순서·중복 정의
  7. 완전한 CMake 링크 에러 예제
  8. 디버깅 단계
  9. 자주 발생하는 에러와 해결법
  10. 프로덕션 패턴
  11. 자주 묻는 질문 (FAQ)

1. 에러 메시지 읽기

Unix (GCC/Clang)

  • undefined reference to 함수명 또는 undefined reference to 네임스페이스::클래스::메서드
  • 어떤 심볼이 정의를 찾지 못했는지가 그대로 나옵니다. 함수명을 복사해 “선언은 어디 있는지”, “구현은 어디 있어야 하는지”를 찾습니다.

MSVC

  • LNK2019: unresolved external symbol “함수 시그니처”
  • 시그니처가 데코레이션(이름 맹글링)된 형태로 나오므로, “선언과 정의의 시그니처가 일치하는지”(const, 참조, 네임스페이스 등)를 확인합니다.

2. 원인 1: 정의 누락·다른 파일에만 구현

상황

  • 헤더에는 함수·클래스 선언이 있는데, 구현(.cpp) 이 없거나, 빌드에 포함되지 않은 경우입니다.
  • 선언만 있는 함수를 호출하면 컴파일은 통과(선언이 있으므로)하고, 링크 시 “정의를 못 찾음”이 됩니다.

해결

  • 구현을 해당 .cpp에 추가하고, 그 .cppadd_executable 또는 add_library 에 들어가 있는지 확인합니다.
  • CMake: target_sources(my_target PRIVATE my_impl.cpp)my_impl.cppmy_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_directoriesfoo 로 링크. 가능하면 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은 libssllibcrypto 두 라이브러리로 나뉘며, 둘 다 링크해야 합니다.

해결:

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 XX가 누락된 심볼.
  • 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는 자동으로 추가하지만, 수동 빌드 시 누락되기 쉽습니다.
  • 증상: 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 -lfoolink_directories, find_library, CMAKE_TOOLCHAIN_FILE
Could not find ... FooCMAKE_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 라이브러리 함수가 unresolvedC++ 맹글링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·씬 그래프·입력 처리 완전 가이드