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
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:
Link — undefined reference to Vec::add(int const&).
Correct paths:
1. Put everything in the header (.hpp/.tpp):
2. Explicit instantiations in a .cpp:
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.
Georg Brandt
(Edited)
Templated static data members — same story: define in one TU or use inline variables (C++17).
Felix Schmidt
(Edited)
If you’ve got lots of cross‑deps, split into .tpp and include from .hpp.
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
MSVC
CMake
Or define the function in the header (only if it’s small/inline):
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
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:
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:
Or provide explicit instantiations in a .cpp:
6) Windows DLL: export/import and the import library
Symptom (MSVC): LNK2019 when calling a function from a DLL.
Check:
Export in the library:
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:
9) Debugging: how to learn what exactly is missing
GCC/Clang: inspect undefined symbols:
readelf/objdump:
MSVC:
Ask the linker to reject undefineds:
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.
Georg Brandt
(Edited)
+1 for the virtual destructor item — it’s bitten me more than once.
Felix Schmidt
(Edited)
--start-group/--end-group saves you when A↔B cycles, but please fix the architecture instead.
Niklas Wagner
(Edited)
And don’t forget LTO can change link behavior and hide issues until release 🙃
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:
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.
Ralf Meier
(Edited)
+1 for CRT: I hit LNK2019 exactly because of /MDd vs /MD between a lib and the app.
Uwe Lehmann
(Edited)
And don’t forget x86/x64: half the ‘magic’ comes from that.
Dieter Hoffmann
(Edited)
Show the exact link command or the Linker → Input tab in MSVC. Otherwise we’ll be guessing forever.
Andreas Keller
(Edited)
In MSVC I just hit Build → Build Solution. In gcc it’s exactly g++ main.cpp as above.
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.
Uwe Lehmann
(Edited)
With MSVC it can be a DLL thing: missing import (__declspec(dllimport) and the import .lib). Static is a different story.
Jens Bauer
(Edited)
Also: are you sure this isn’t a C library you’re calling from C++? Then you need extern "C".
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.