Unresolved external symbol” — why is the linker yelling and how do I calm it down? (LNK2019/LNK2001, undefined reference)

Body:
I’m building a small C++ project and at the link stage I hit one of the usual errors:

  • “unresolved external symbol …”
  • “undefined reference to symbol …”
  • “unresolved external symbol …” / “undefined reference to …”
  • “error LNK2019 …” / “LNK2001: unresolved external symbol …”

Minimal example:

		
// foo.h #pragma once void foo();

// main.cpp #include "foo.h" int main() { foo(); }

Compile & link:

  • MSVC: cl /EHsc main.cpp → LNK2019: unresolved external symbol "void __cdecl foo(void)" referenced in function main
  • GCC/Clang: g++ -std=c++17 -O2 -o app main.cpp → /usr/bin/ld: undefined reference to 'foo()'

I tried adding a static library mylib to the project (it “supposedly” has the implementation), but no luck. Also tried with CMake:

		
add_executable(app main.cpp) # add_library(mylib foo.cpp) # <-- maybe this is wrong? # target_link_libraries(app PRIVATE mylib)

I’ve also seen similar messages when attaching a third‑party .lib/.a or dll/so.

Question: what exactly do these linker errors mean and how do I figure out, step by step, what the linker is missing? What are the typical causes (wrong library order, mismatched signature, templates, extern "C", __declspec(dllimport), etc.) that most often lead to this — and how do I fix them?

Answers

  • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

    Thomas Braun

    (Edited)

    Templates, inlines, and ODR: why “it’s all in .cpp” but the linker still doesn’t believe you

    Classic pain: “I defined methods of a templated class in a .cpp, yet ld/Linker screams undefined reference.” The simple explanation: templates generate code at instantiation, and definitions must be visible at the point of use.

    Anti‑example:

    		
    // vec.hpp template struct Vec { void add(const T&); };

    // vec.cpp #include "vec.hpp" template void Vec::add(const T&) { /*...*/ }

    // main.cpp #include "vec.hpp" int main(){ Vec v; v.add(1); }

    Link — undefined reference to Vec::add(int const&).

    Correct paths:
    1. Put everything in the header (.hpp/.tpp):

    		
    // vec.hpp template struct Vec { void add(const T&); };

    template void Vec::add(const T&) { /*...*/ }

    2. Explicit instantiations in a .cpp:

    		
    // vec.cpp #include "vec.hpp" template void Vec::add(const int&);

    Inline functions: if you put implementation in a header without inline, you’ll get LNK2005 (multiple definitions).
    ODR: one and only one definition in the entire program. Watch for duplicates.

    Bonus trap: a virtual function is declared but not defined (even if empty) → unresolved vtable. Provide A::~A() = default; as in A1.

    0
    • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

      Georg Brandt

      (Edited)

      Templated static data members — same story: define in one TU or use inline variables (C++17).

    • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

      Felix Schmidt

      (Edited)

      If you’ve got lots of cross‑deps, split into .tpp and include from .hpp.

  • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

    Dieter Hoffmann

    (Edited)

    Unresolved/undefined — that’s not the compiler, that’s the linker. A checklist of causes and quick fixes

    In short:
    the compiler produces object files with “promises” (symbols), and the linker glues them together and substitutes actual definitions. An “unresolved external symbol” means: somewhere you promised a function/variable exists, but the linker did not find its definition in the objects/libraries you provided.

    Below are the top causes and how to fix them.

    1) There is a declaration, but no definition
    Symptom: as in your example: foo() is declared in foo.h, called from main.cpp, but foo.cpp is not built/not added to the link.
    Fix:
    Add foo.cpp to the build:

    GCC/Clang

    		
    g++ -std=c++17 -O2 -o app main.cpp foo.cpp

    MSVC

    		
    cl /EHsc main.cpp foo.cpp

    CMake

    		
    add_executable(app main.cpp foo.cpp)

    Or define the function in the header (only if it’s small/inline):

    		
    // foo.h inline void foo() { /*...*/ }

    2) The library exists, but you aren’t linking it / wrong path
    Symptom: the implementation is in libmylib.a / mylib.lib, but the linker can’t see it.

    Fix:
    CMake

    		
    add_library(mylib STATIC foo.cpp) target_link_libraries(app PRIVATE mylib)

    MSVC (GUI):
    Project → Properties → Linker → Input → Additional Dependencies → mylib.lib; Linker → General → Additional Library Directories — path to the .lib.

    GCC/Clang
    g++ main.cpp -Lpath/to/lib -lmylib -o app

    3) Order of static libraries (ld) — A depends on B
    ymptom:S in GNU ld the order matters: first the object/library that uses the symbols, then the library that provides them.

    Bad:
    g++ -o app main.o -lB -lA # A depends on B — won’t find

    Good:
    g++ -o app main.o -lA -lB

    Complex cyclic dependencies:
    g++ main.o -Wl,--start-group -lA -lB -Wl,--end-group -o app

    4) Signature mismatch — that’s a DIFFERENT symbol
    Symptom: you defined void foo(int) but you’re calling foo(). Or const‑ness/namespace/noexcept differs.
    C++ “mangles” names: any type difference → a new symbol.

    Check:

    		
    // foo.h void foo(); // foo.cpp void foo(int) {} // <-- different symbol

    Fix the signatures, rebuild everything.

    5) Templates and inline functions: definitions must be VISIBLE
    Symptom: undefined reference to MyVec::push_back even though you “defined” methods in a .cpp.
    Reason: for templates, definitions must be available at the point of instantiation (usually in the .h). Otherwise the linker will never see a generated implementation.

    Fixes:
    Move definitions into the header:

    		
    // myvec.h template struct MyVec { void push_back(const T&); };

    template void MyVec::push_back(const T&) { /*...*/ }

    Or provide explicit instantiations in a .cpp:

    		
    // myvec.cpp template struct MyVec; template void MyVec::push_back(const int&);

    6) Windows DLL: export/import and the import library
    Symptom (MSVC): LNK2019 when calling a function from a DLL.

    Check:
    Export in the library:

    		
    // api.h #ifdef BUILDING_MYDLL # define API __declspec(dllexport) #else # define API __declspec(dllimport) #endif

    extern "C" API void foo();

    In the consumer link the import .lib (Linker → Input → Additional Dependencies).

    Do architectures match (x64 vs x86) and configurations (Debug/Release, CRT /MD vs /MT)?

    7) C vs C++: name mangling and extern "C"
    Symptom: you include a C header in C++ code, and ld/Linker can’t find the symbol (cos, my_c_func, etc.).

    Fix:
    extern "C" {
    #include "mylib_c_api.h"
    }

    And don’t forget the library itself: GCC often needs -lm for :
    g++ main.cpp -lm -o app

    8) Vtables/destructors: declared — not defined
    Symptom (MSVC): LNK2019 unresolved external symbol "public: __thiscall A::~A(void)" ...

    Reason: you have virtual ~A(); with no definition, even if the body is empty.

    Fix:

    		
    struct A { virtual ~A(); }; A::~A() = default; // or { }

    9) Debugging: how to learn what exactly is missing

    GCC/Clang: inspect undefined symbols:

    		
    nm -u main.o nm -A libmylib.a | grep foo

    readelf/objdump:

    		
    readelf -Ws libmylib.a | grep foo objdump -t libmylib.a | grep foo

    MSVC:

    		
    dumpbin /SYMBOLS mylib.lib | find "foo"

    Ask the linker to reject undefineds:

    		
    -Wl,--no-undefined

    10) Rare but real

    Wrong calling convention: __cdecl vs __stdcall/__vectorcall.

    A silly typo in the name/namespace.

    Missing main/WinMain (or wrong /SUBSYSTEM).

    ABI mismatch (different compilers/library versions).

    Summary:
    the error means “you promised a symbol — no definition found.” Walk the checklist: (1) add the needed .cpp or target_link_libraries, (2) static lib order, (3) signature/ABI match, (4) template visibility, (5) DLL export/import, (6) extern "C" and -lm. Tools like nm/readelf/dumpbin quickly tell you which symbol went missing.

    0
    • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

      Georg Brandt

      (Edited)

      +1 for the virtual destructor item — it’s bitten me more than once.

    • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

      Felix Schmidt

      (Edited)

      --start-group/--end-group saves you when A↔B cycles, but please fix the architecture instead.

    • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

      Niklas Wagner

      (Edited)

      And don’t forget LTO can change link behavior and hide issues until release 🙃

  • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

    Markus Neumann

    (Edited)

    MSVC specifics: what to click, what to type, and why Debug x64 won’t play with Release Win32

    If you’re on Visual Studio, here’s the quick path without sorcery:
    1. Check configuration and platform
    Top toolbar: Debug/Release and x64/Win32. A library built for x86 won’t link to an x64 app (and vice versa).

    2.Add the .cpp to the project
    Right‑click the project → Add → Existing Item… → pick foo.cpp.
    Don’t put implementation in a header without inline — you’ll get LNK2005 (different story).

    3. Attach the .lib if it’s an external DLL
    Properties → Linker → Input → Additional Dependencies → mylib.lib.
    Properties → Linker → General → Additional Library Directories → path to the .lib directory.

    4. CRT/Runtime
    Mixing /MD vs /MT (dynamic vs static runtime) often wrecks the build.
    See: C/C++ → Code Generation → Runtime Library — make exe and libs match.

    5./SUBSYSTEM and entry point
    For a console app you need main and Linker → System → Subsystem = Console.
    For GUI with WinMain — set Windows.

    6. See what’s missing
    dumpbin /LINKERMEMBER mylib.lib and /SYMBOLS — check whether the right (mangled!) name is exported.

    Short example:

    		
    // api.h #ifdef BUILDING_DLL # define API __declspec(dllexport) #else # define API __declspec(dllimport) #endif

    extern "C" API void foo();

    In the DLL project: Properties → C/C++ → Preprocessor → define BUILDING_DLL = 1.

    In the EXE project: don’t define BUILDING_DLL, add mylib.lib under Linker → Input.

    And yes: sometimes VS “forgets” deps. Recreate the Solution, check Project Dependencies.

    0
    • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

      Ralf Meier

      (Edited)

      +1 for CRT: I hit LNK2019 exactly because of /MDd vs /MD between a lib and the app.

    • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

      Uwe Lehmann

      (Edited)

      And don’t forget x86/x64: half the ‘magic’ comes from that.

  • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

    Dieter Hoffmann

    (Edited)

    Show the exact link command or the Linker → Input tab in MSVC. Otherwise we’ll be guessing forever.

    0
    • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

      Andreas Keller

      (Edited)

      In MSVC I just hit Build → Build Solution. In gcc it’s exactly g++ main.cpp as above.

      • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

        Dieter Hoffmann

        (Edited)

        Then you simply don’t have a definition of foo(). You declared it in the header but didn’t link the object/library where the implementation (foo.cpp) lives.

  • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

    Uwe Lehmann

    (Edited)

    With MSVC it can be a DLL thing: missing import (__declspec(dllimport) and the import .lib). Static is a different story.

    0
  • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

    Jens Bauer

    (Edited)

    Also: are you sure this isn’t a C library you’re calling from C++? Then you need extern "C".

    0
  • Gravatar — онлайн-сервис, позволяющий пользователям Интернета поддерживать постоянную картинку на большинстве сайтов.

    Ralf Meier

    (Edited)

    If you link static libs, check the order: with gcc/ld the dependent target must come before the library it depends on.

    0