🧩 What Problem Does extern "C" Solve?
#
In mixed C/C++ codebases, you will often see headers structured like this:
#ifdef __cplusplus
extern "C" {
#endif
/* declarations */
#ifdef __cplusplus
}
#endif
If a header is only ever consumed by C, this construct is irrelevant. The moment any C++ translation unit includes it, however, this pattern becomes mandatory for correct linking.
The key is the predefined macro __cplusplus:
- Required by the C++ standard
- Defined by all conforming C++ compilers
- Not defined by C compilers
This allows a single header to remain valid in both languages.
🧠 The Root Cause: C++ Name Mangling #
C++ supports:
- Function overloading
- Namespaces
- Templates
To support these features, C++ compilers perform name mangling—encoding type and scope information into symbol names emitted to the object file.
Example:
void foo(int);
Might become (Itanium ABI example):
_Z3fooi
C, by contrast:
- Has a single global namespace
- Does not support overloading
- Emits unmangled (or minimally decorated) symbols
foo
The Linker Failure Scenario #
If:
- A function is compiled as C
- But declared as C++ (without
extern "C")
Then:
- The C++ compiler expects
_Z3fooi - The object file only contains
foo - Result: undefined symbol at link time
This has nothing to do with syntax or semantics—it is purely an ABI mismatch.
🔗 What extern "C" Actually Means
#
extern "C" is a linkage specification, not a language switch.
It tells the C++ compiler:
“Emit symbols using C linkage rules.”
Effects:
- Disables name mangling
- Forces C-style symbol naming
- Does not change calling convention semantics at the language level
Valid Usage Patterns #
Single Declaration #
extern "C" void foo();
Block Declaration (Preferred for Headers) #
extern "C" {
void foo(void);
int bar(int);
}
This ensures binary compatibility between C and C++ object files.
⚠️ A Common Trap: #include Inside extern "C"
#
Placing #include directives inside an extern "C" block is dangerous and strongly discouraged.
Risk 1: Linkage Nesting Pathologies #
If:
b.hwraps#include "a.h"inextern "C"- And
a.halready has its own linkage guards
You create nested linkage specifications.
While legal per the standard (innermost wins), this:
- Increases compiler complexity
- Has historically triggered bugs (notably older MSVC versions)
- Makes debugging linkage issues painful
Risk 2: Silent ABI Corruption (The Real Danger) #
If a.h contains pure C++ declarations, including it inside an extern "C" block:
- Forces C linkage on symbols that must be mangled
- Breaks linkage everywhere else in the program
- Causes linker errors that appear unrelated to the real mistake
This kind of bug is subtle, global, and expensive to diagnose.
Rule:
Never change the linkage of code you do not fully control.
📐 What Belongs Inside extern "C"?
#
Strictly speaking, only entities with linkage:
- Function declarations
- Global variable declarations
- Typedefs of function pointers
Entities allowed but unaffected:
structenumtypedef(non-function)
The linkage specification has no semantic effect on these types—it only matters for symbols emitted to the linker.
🔄 Calling Across the Language Boundary #
C++ Calling C (Most Common) #
- Wrap C declarations in
extern "C"in the header - Compile the implementation with a C compiler
C Calling C++ (Less Common, More Fragile) #
C does not understand extern "C".
Correct pattern:
-
C++ source file
extern "C" int cpp_wrapper(int x) { return real_cpp_function(x); } -
C source file
extern int cpp_wrapper(int);
Never include a C++ header directly in C code.
❓ Macro Guard Nuances #
Q: Is #ifdef __cplusplus sufficient?
Yes—for virtually all modern compilers.
Only extremely old or non-conforming toolchains require checking a numeric value via #if __cplusplus.
Best practice: use #ifdef __cplusplus for clarity and portability.
📋 Final Summary #
| Scenario | Best Practice |
|---|---|
| C++ → C | Use extern "C" around C declarations |
| C → C++ | Provide C-linkage wrapper functions |
| Public Headers | Always guard with #ifdef __cplusplus |
| Includes | Keep #include outside linkage blocks |
| Mental Model | extern "C" controls ABI, not language |
🧭 Takeaway #
extern "C" is not about syntax—it is about binary contracts.
Misusing it won’t cause compiler errors; it will cause linker failures, ABI mismatches, or worse: silent ODR violations.
In large systems—kernels, RTOSes, drivers, and firmware—getting this right is the difference between a clean build and a week of linker archaeology.