Abi Linking
By the end of this lesson you'll be able to read and fix the two linker errors that stop every real C++ project — "undefined reference" and "multiple definition" — explain the difference between a declaration and a definition, control whether a name is private to one file or shared across files, and understand why mixing compilers or STL versions corrupts a build.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Think of building a program like assembling a piece of flat-pack furniture from several boxes. Compiling is each box being made on its own production line — the factory only needs the instruction sheet (a declaration : "part B exists and bolts here") to make a box, not the actual part. Linking is opening every box at home and bolting the real parts together (the definitions ). If a part the instructions promised is in no box, you get "undefined reference" — a missing piece. If two boxes each contain the same uniquely-numbered part, you get "multiple definition" — a duplicate the assembler refuses. And the ABI is the agreed bolt size and hole spacing: if one box was made to metric and another to imperial, nothing fits even though both look like furniture.
1. Compiling vs Linking — Two Jobs, Two Errors
Building a C++ program is a pipeline. The preprocessor expands every #include and #define into one big translation unit ; the compiler turns each .cpp into an object file ( .o or .obj ) on its own; the linker stitches those object files (plus any libraries) into the final executable. The crucial insight: each .cpp is compiled alone , so the compiler only needs a declaration — a promise that a function exists — to accept a call to it. Finding the real definition is the linker's job, later. That split is why "compiler error" and "linker error" mean very different things.
2. Name Mangling — Why Overloading Works
C allows only one function per name. C++ supports overloading — several functions sharing a name but differing in their parameters. The linker, though, only sees a flat list of symbol names with no notion of types. So the compiler mangles each name, encoding the parameter types into the symbol, which is how process(int) and process(double) become two distinct symbols the linker can tell apart. You'll meet these mangled names whenever you read a linker error.
Pro Tip: Demangle any symbol with c++filt : c++filt _Z7processi prints process(int) . Run nm -C ./app to list every symbol in a binary already demangled — invaluable when an error mentions a name like _ZN4math3addEii .
When you need C code (or an OS API, or dlopen ) to call your function, mangling gets in the way — C has no mangling, so the names must match exactly. extern "C" tells the compiler to emit a plain, unmangled symbol. The trade-off: because the type info is gone, an extern "C" function cannot be overloaded.
Your turn. The program below promises a function exists (a declaration) and calls it, but its body is missing — the exact setup that triggers "undefined reference". Fill in the two blanks marked ___ to call it and to supply the definition.
3. Internal vs External Linkage & the One Definition Rule
Linkage decides who can see a name across files. A name with internal linkage is private to one .cpp file — give it that with an anonymous namespace (the modern way) or file-scope static , and two files can each have their own same-named helper with no clash. A name with external linkage is visible to every file; you reach a variable defined elsewhere by declaring it extern . Tying it together is the One Definition Rule (ODR) : you may declare a name as often as you like, but you must define it exactly once across the whole program (unless it is inline or a template, which are allowed to repeat and get merged).
Now you try. The helper below would collide with a same-named helper in another file because it has external linkage. Wrap it so it becomes private to this file:
4. The Linker Errors You'll Actually Hit
Almost every real linker error is one of three things: an undefined reference (declared and used, but never defined or never linked), a multiple definition (the same symbol defined in two translation units — usually a non- inline definition placed in a header that two files included), or a declaration/definition mismatch (the signatures disagree, so the mangled names differ and the call resolves to nothing). The worked example shows each, with the comment spelling out the message and the fix.
5. ABI Stability — Why Mixing Compilers Breaks
The ABI (Application Binary Interface) is the binary contract two object files must agree on: how names are mangled, how arguments are passed in registers or on the stack, and how a class or a standard-library type like std::string is laid out in memory. Source-level (API) compatibility is not enough — if one binary thinks std::string is 32 bytes and another thinks it is 24, a string passed across that boundary reads the wrong memory and crashes. That is why everything in a program, including every library, must be built with one compiler and one standard-library version.
Headers carry promises, source files carry bodies. Put declarations in the .h (function prototypes, extern variable declarations, class definitions, type aliases) and the single definition in one .cpp . Break that and the same symbol lands in every file that includes the header — the classic "multiple definition".
The escape hatch is inline : an inline function or variable is allowed to be defined in multiple translation units, and the linker merges the copies into one. That is exactly why header-only libraries mark their definitions inline , and why constexpr (which is implicitly inline ) and templates may live entirely in headers.
No blanks this time — just a brief and a blank canvas (with an outline to keep you on track). Apply the three rules from this lesson in a single program: a declaration that promises a function, a file-private helper with internal linkage, and exactly one definition. Run it and check your output against the example in the comments.
Practice quiz
During a C++ build, whose job is it to find the actual definition (body) of a function?
- The preprocessor
- The compiler
- The linker
- The loader at runtime
Answer: The linker. Each .cpp compiles alone needing only a declaration; the linker later matches calls to their definitions across object files.
You see 'undefined reference to foo()'. What kind of error is this?
- A linker error
- A compiler (syntax) error
- A preprocessor error
- A runtime exception
Answer: A linker error. The declaration let it compile, but the linker found no matching definition to link against.
Why does C++ mangle function names like process(int) into symbols such as _Z7processi?
- To encrypt the binary
- To make names shorter
- To support runtime reflection
- To encode parameter types so overloads become distinct symbols
Answer: To encode parameter types so overloads become distinct symbols. The linker only sees flat names, so the parameter types are baked into the symbol to keep overloads apart.
What does extern "C" do to a C++ function?
- Makes it run faster
- Gives it C linkage with a plain, unmangled symbol name
- Forces it to be inline
- Allows it to be overloaded
Answer: Gives it C linkage with a plain, unmangled symbol name. extern "C" emits the plain name (like c_add) so C code and OS APIs can find it; the trade-off is no overloading.
What is the difference between a declaration and a definition?
- A declaration introduces a name/type; a definition creates the actual body or storage
- They are the same thing
- A definition is only for variables
- A declaration allocates memory
Answer: A declaration introduces a name/type; a definition creates the actual body or storage. You may declare a name many times, but define it exactly once across the whole program.
What is the One Definition Rule (ODR)?
- Every file needs exactly one function
- Headers may only hold one declaration
- A name may be defined only once across the program (unless inline/template)
- Each .cpp may include a header only once
Answer: A name may be defined only once across the program (unless inline/template). Declare freely, but define once — inline functions and templates are the allowed exceptions and get merged.
Which gives a name INTERNAL linkage (private to one .cpp file)?
- extern
- An anonymous namespace or file-scope static
- inline
- A forward declaration
Answer: An anonymous namespace or file-scope static. An anonymous namespace (preferred) or file-scope static makes a name private so it can never clash across files.
You get 'multiple definition of config' linking two files that include the same header. The likely fix is:
- Add #pragma once only
- Compile with -O2
- Rename the header
- Mark the variable inline, or declare it extern and define it in one .cpp
Answer: Mark the variable inline, or declare it extern and define it in one .cpp. A non-inline definition in a header lands in every including TU; inline merges duplicates, or move the single definition to one .cpp.
Why can mixing GCC and Clang builds, or two STL versions, break a program at link or run time?
- They use different keywords
- They can disagree on the ABI: name mangling and how types like std::string are laid out
- Clang cannot link at all
- Only one compiler can be installed
Answer: They can disagree on the ABI: name mangling and how types like std::string are laid out. If one side thinks std::string is 32 bytes and the other 24, passing it across the boundary reads the wrong memory.
Which keyword lets the SAME function definition appear in many translation units and be merged into one?
- static
- extern
- inline
- constexpr only
Answer: inline. inline tells the linker duplicate copies are allowed and should be deduped — which is why header-only libraries mark definitions inline.