Code Size Comparison: Contracts vs Assert¶
Overview¶
This document compares the code size impact of C++26 Contracts using this ABI
specification versus the traditional assert() macro.
Call Site Comparison¶
Classic assert()¶
The assert(x > 0) macro typically expands to:
if (!(x > 0)) {
__assert_fail("x > 0", __FILE__, __LINE__, __func__);
}
Generated assembly (x86-64):
; Condition check
cmp edi, 0 ; 3 bytes
jg .L_ok ; 2 bytes
; Violation path (4 arguments)
lea rdi, [rip + .L_expr] ; 7 bytes - "x > 0"
lea rsi, [rip + .L_file] ; 7 bytes - "file.cpp"
mov edx, 42 ; 5 bytes - line number
lea rcx, [rip + .L_func] ; 7 bytes - "func_name"
call __assert_fail ; 5 bytes
.L_ok:
Call site size: ~36 bytes
C++26 Contracts (Generic Entrypoint)¶
Using the 6-parameter generic entrypoint:
; Condition check
cmp edi, 0 ; 3 bytes
jg .L_ok ; 2 bytes
; Violation path (6 arguments)
lea rdi, [rip + .L_descriptor] ; 7 bytes
lea rsi, [rip + .L_static_data] ; 7 bytes
mov edx, 0x01 ; 5 bytes - mode
mov ecx, 0x01 ; 5 bytes - semantic
xor r8d, r8d ; 3 bytes - dynamic_data
xor r9d, r9d ; 3 bytes - reserved
call __cxa_contract_violation_entrypoint ; 5 bytes
.L_ok:
Call site size: ~40 bytes
C++26 Contracts (Runtime Wrapper)¶
Using the 2-parameter runtime wrapper:
; Condition check
cmp edi, 0 ; 3 bytes
jg .L_ok ; 2 bytes
; Violation path (2 arguments)
lea rdi, [rip + .L_descriptor] ; 7 bytes
lea rsi, [rip + .L_static_data] ; 7 bytes
call __cxa_contract_violation_pf_se ; 5 bytes
.L_ok:
Call site size: ~24 bytes
C++26 Contracts (Compiler-Generated Wrapper)¶
Using single-pointer compiler-generated wrapper:
; Condition check
cmp edi, 0 ; 3 bytes
jg .L_ok ; 2 bytes
; Violation path (1 argument)
lea rdi, [rip + .L_static_data] ; 7 bytes
call contract_violation_pf_se ; 5 bytes
.L_ok:
Call site size: ~17 bytes
Summary¶
Approach |
Call Site |
vs assert() |
|---|---|---|
|
36 bytes |
baseline |
Contracts (generic, 6 params) |
40 bytes |
+11% |
Contracts (runtime wrapper, 2 params) |
24 bytes |
-33% |
Contracts (compiler wrapper, 1 param) |
17 bytes |
-53% |
Static Data Comparison¶
assert()¶
Each assert site requires string literals:
Data |
Typical Size |
Shared? |
|---|---|---|
Expression string |
~20 bytes |
No (unique per site) |
File name string |
~30 bytes |
Yes (per file) |
Function name string |
~20 bytes |
Partial (per function) |
Per-site static data: ~20-70 bytes
Contracts¶
Each contract site requires a static_data blob:
Data |
Size (LP64) |
Shared? |
|---|---|---|
|
24 bytes |
No |
Source text pointer |
8 bytes |
No |
Assertion kind |
1 byte |
No |
File name string |
~30 bytes |
Yes (per file) |
Function name string |
~20 bytes |
Partial |
Source text string |
~20 bytes |
No |
Per-site static data: ~33 bytes (struct) + ~20 bytes (source text)
The descriptor table is shared across all contracts in a translation unit, adding negligible overhead (~50-100 bytes per TU).
Program-Wide Impact¶
For a program with 10,000 contract sites:
Approach |
Call Sites |
Wrappers |
Total Code |
|---|---|---|---|
assert() |
360 KB |
0 |
360 KB |
Contracts (generic) |
400 KB |
0 |
400 KB |
Contracts (runtime wrapper) |
240 KB |
~0.1 KB |
240 KB |
Contracts (compiler wrapper) |
170 KB |
~0.5 KB |
170 KB |
Using compiler-generated wrappers, contracts achieve 53% smaller code than traditional assert().
Additional Benefits¶
Beyond code size, contracts provide:
[[noreturn]]OptimizationEnforced contract wrappers are marked
[[noreturn]], allowing the compiler to eliminate dead code after violation calls.- Unified Handler
All contract violations go through a single customizable handler, unlike assert() which always calls
abort().- Rich Metadata
Contracts carry structured metadata (assertion kind, evaluation semantic, detection mode) enabling sophisticated violation handling.
- Deduplication
Descriptor tables and string literals can be deduplicated by the linker, further reducing binary size.