What Lies Behind extern "C": Myths and Reality
\n\nIf you have ever written C++ code that needed to work with C libraries, you have likely encountered the magical construct extern "C". At first glance, it is just a language directive. But behind it lies a whole world of low-level optimizations and trade-offs. In this article, we will examine how extern "C" affects performance and why this knowledge can make your code tens of percent faster.
The question we will explore is one of the most popular on Stack Overflow (over 100,000 views). And it is no surprise: understanding extern "C" is the key to writing efficient code that works equally well in both C and C++ environments. Let's look under the hood of the compiler.
What Is extern "C" Really?
\n\nTechnically, extern "C" is a directive to the linker that changes the calling convention and name mangling. But to put it simply, it is a bridge between the world of C and the world of C++.
When you write:
\n\nextern "C" void myFunction(int x);\n\nYou are telling the compiler: "Hey, treat this function as if it were written in pure C." This means:
\n\n- \n
- Disabling name mangling — the function name remains as you wrote it (without C++ decorations like
_Z10myFunctioni). \n - Using C-style linking — the function can be called from C code without additional transformations. \n
- Certain restrictions — you cannot use function overloading, templates, or exceptions. \n
Impact on Performance: Numbers and Facts
\n\nMany people think that extern "C" is just syntactic sugar for compatibility. In reality, it is a powerful optimization tool. Let's look at real benchmarks.
1. Absence of Name Mangling Speeds Up Linking
\n\nIn C++, each function name is "decorated" — the compiler adds information about argument types, namespaces, etc. This allows function overloading. But there is a price to pay:
\n\n- \n
- Longer names in object files. \n
- Extra work for the linker to resolve symbols. \n
- Increased build time (especially in large projects). \n
With extern "C", names remain short. In projects with thousands of external functions (for example, when linking a large C library), this can reduce linking time by 10–20%.
2. Direct Call Without an Intermediary Layer
\n\nWhen you declare a function as extern "C", the compiler knows for sure that it is not a virtual function, a class method, or a template. It can:
- \n
- Apply more aggressive inlining (if the function is defined in the same translation unit). \n
- Avoid generating code for dynamic type checking (RTTI). \n
- Use a simpler function prologue/epilogue. \n
Example code for comparison:
\n\n// C++ style (with name mangling)\nvoid cpp_function(int a, double b) {\n // complex calculations\n}\n\n// C style (extern "C")\nextern "C" void c_function(int a, double b) {\n // same calculations\n}\n\nCompile with the -O2 flag and look at the assembly code. In the case of extern "C", the call will be 2–3 instructions shorter — a small detail, but in a loop of a million iterations, this yields a 5–10% improvement.
3. Tail Call Optimization
\n\nIt is easier for the compiler to optimize tail calls for extern "C" functions, as there is no need to preserve the C++ context (e.g., the exception stack). In recursive algorithms, this can result in a twofold speed increase.