SimplifyC++ Article
Calling Conventions in C, C++, and Rust
Calling Conventions in C, C++, and Rust
A Deep Technical Analysis of Their Impact on the Final ABI
1. Introduction: Calling Convention vs ABI
Before comparing C, C++, and Rust, we must separate two frequently confused concepts:
Calling Convention
Defines:
How arguments are passed (registers vs stack)
How return values are delivered
Which registers are caller/callee saved
Stack alignment requirements
Who cleans the stack
ABI (Application Binary Interface)
Much broader. Includes:
Calling convention
Type sizes and alignment
Struct/class layout
Name mangling rules
Exception handling model
Object file format and symbol visibility
Dynamic linking rules
Calling convention is a component of the ABI — not the whole ABI.
2. The Platform Controls the Real ABI
On modern systems, the platform ABI defines the real low-level rules:
Examples:
SysV AMD64 ABI (Linux/macOS/BSD on x86-64)
Windows x64 ABI
AAPCS64 (AArch64)
C, C++, and Rust compilers generally conform to the platform ABI when generating machine code.
So in practice:
The architecture + operating system define the low-level calling convention. The language defines how much additional ABI surface it adds.
3. C: The Minimal ABI Surface
C is often considered the "binary lingua franca" because:
No name mangling
No implicit runtime
No class layout rules
No exceptions crossing boundaries
Simple type system
A C function:
int add(int a, int b);Will:
Follow the platform calling convention
Export a predictable symbol name
Have stable parameter passing rules
Why C Is Stable in Practice
Because:
It adds almost nothing beyond the platform ABI.
All major compilers target the same system ABI contracts.
Its type system maps cleanly to machine-level constructs.
Where C Still Requires Care
LP64 vs LLP64 models (
longdiffers)Struct padding/alignment
Bitfields
Compiler-specific attributes
Variadic functions
But overall, C offers the narrowest and safest binary interface.
4. C++: Same Calling Convention, Larger ABI Surface
4.1 Function-Level Calling Convention
For simple free functions:
int add(int a, int b);The actual register usage and stack layout are typically identical to C on the same platform.
So at the lowest level:
C++ often shares the same calling convention as C.
But this is only part of the story.
4.2 What C++ Adds to the ABI
C++ introduces additional binary obligations:
1. Name Mangling
Encodes:
Function overloading
Namespaces
Templates
Different compilers may use different mangling schemes.
2. Class Layout Rules
Includes:
Padding and alignment
Multiple inheritance layout
Empty base optimization
Virtual base offsets
Layout may differ across:
Compilers
Compiler versions
Flags
3. VTables and RTTI
Virtual functions require:
vptr placement
vtable layout rules
Runtime type information metadata
These are ABI-defined — but not standardized across all toolchains.
4. Exception Handling and Unwinding
C++ requires:
Stack unwinding metadata
Personality functions
Exception object format
Cross-compiler binary compatibility becomes fragile.
4.3 Result
C++ often uses the same low-level calling convention as C, but:
The C++ ABI is much larger and more complex than C’s ABI.
Therefore:
Stable cross-compiler C++ binary interfaces are difficult.
Public C++ shared libraries must tightly control toolchains.
5. Rust: Explicit ABI Choice
5.1 Default Rust ABI
Rust functions without an extern declaration use the "Rust" ABI.
Example:
pub fn add(a: u64, b: u64) -> u64 { a + b }This does NOT guarantee:
Stable calling convention across compiler versions
Stable symbol naming
Stable layout
Rust does not promise a stable native ABI.
5.2 Rust Opt-In ABI
Rust forces you to be explicit:
C ABI
extern "C" fn add(a: u64, b: u64) -> u64 { a + b}Now:
Uses platform C calling convention
Exports unmangled symbol
Suitable for FFI
C-Compatible Layout
struct Foo { x: u32, y: u64,}Ensures:
Field ordering matches C
Padding rules follow C ABI
Unwinding Control
Rust separates:
"C"ABI (no unwinding expected)"C-unwind"ABI (explicit cross-language unwinding)
This is critical for safety.
5.3 Result
Rust does not define a universal stable ABI.
Instead:
Rust delegates stable binary interfaces to the C ABI.
Rust’s strength is explicitness:
ABI is opt-in.
Layout is opt-in.
Unwinding is opt-in.
6. Real-World Failure Modes in Final ABI
Even if you "use C everywhere", ABI mismatches can occur due to:
1. Platform ABI Differences
Windows x64 vs SysV AMD64
AArch64 differences
2. Struct Return Rules (sret)
Large struct return conventions vary.
3. Floating-Point and Vector Registers
Incorrect prototypes can corrupt state.
4. Exception / Panic Crossing
C++ exceptions crossing into Rust
Rust panic crossing into C
Undefined behavior if not managed
5. Memory Ownership Contracts
Who allocates? Who frees? Which allocator?
This is part of ABI contract design — not just calling convention.
7. Comparative Summary
| Feature | C | C++ | Rust |
|---|---|---|---|
| Platform calling convention | Yes | Yes | Yes (via extern) |
| Name mangling | No | Yes | Yes (unless no_mangle) |
| Stable default ABI | Yes (practically) | No (complex) | No |
| Explicit ABI control | Limited | Partial | Strong |
| Exception model in ABI | None | Yes | Explicit and separated |
| Recommended for stable FFI | Yes | Only via extern "C" | Only via extern "C" |
8. Engineering Conclusion
C
Smallest ABI surface. Most stable for long-term binary compatibility.
C++
Shares calling convention with C but adds:
Mangling
Vtables
Exceptions
Template instantiation complexity
Binary stability requires discipline.
Rust
No stable default ABI. Provides explicit, opt-in C ABI compatibility.
Best practice: Expose C ABI. Hide Rust internally.
9. Final Engineering Rule
If you want:
Cross-language compatibility
Cross-compiler stability
Long-term binary durability
Then:
Design a C ABI surface. Implement internally in C, C++, or Rust. Never expose language-specific ABI details.
Calling conventions may match at the CPU level — but ABI stability is determined by how much semantic complexity the language leaks into the binary boundary.
What did you think?
Sign in to react or comment.
Comments
0No comments yet.