Stupid Smart Pointers in C
Overall reaction to the “smart pointer” hack
- Many see the return-address–smashing trick as a clever, entertaining experiment, but not something to use in production.
- Concerns include undefined behavior, extreme fragility across compilers/ABIs, and interaction with stack-protection and future CPU mitigations.
- Some view it as emblematic of “C hacks” that are fun, but whose magic is not worth the risk outside toy code.
Portability, optimization, and safety issues
- The approach relies on exact stack layout and presence of a frame pointer; inlining, LTO, stack alignment, shadow stacks, or different ABIs can all break it.
- It likely harms branch prediction (like thread switching), fighting against return-address prediction hardware.
- Stack canaries/StackGuard and microcode security updates may invalidate such tricks unexpectedly.
- Several commenters argue that such stack tricks belong only in hand-written assembly, if anywhere.
Preferred alternatives for resource/memory management in C
- GCC/Clang
__attribute__((cleanup))is widely used to implement scoped cleanup (including locks), but:- It’s non-standard and unsupported by some compilers (notably MSVC, many embedded compilers).
- It doesn’t automatically handle values you want to return; workarounds exist via macros or manually nulling pointers.
- Proposed and upcoming
deferfor C (block-scoped, unlike Go’s function-scoped defer) is discussed as a more principled solution. - Many advocate arena/pool allocators (talloc, APR, obstacks, custom arenas):
- Group objects by lifetime “bucket” and free whole arenas at once.
- Often simpler and faster than tracking thousands of individual object lifetimes.
- Other patterns:
- “Never free” or custom allocators for small or short-lived programs.
- Simple manual patterns: initialize pointers to NULL, allocate, then free all non-NULL (or all) at a single cleanup point.
- Per-thread resource/error state that records all allocations and frees them en masse on exit.
Standard C vs extensions and toolchains
- Disagreement over relying on GCC/Clang extensions: some prioritize long-term portability (including MSVC/embedded), others pragmatically target the major compilers and expect extensions to be standardized later.
- Notes that real-world C already uses platform- and compiler-specific code; fully “pure” standard C is rare.
- C23 changes already broke some existing code, showing that even standards evolution can affect portability.
Reference counting and performance
- Reference counting is called out as bug-prone and sometimes badly matched to modern CPUs.
- Under contention, atomic refcount updates can cause cache-line ping-pong and high latency; even adjacent refcounts sharing a cache line can suffer.
- A GCC plugin exists to automate reference counting; feedback is invited.
Comparisons with C++, Go, Rust, Zig
- Several argue that C++’s real advantage is RAII/destructors, not “smart pointers” per se; with RAII-like constructs in C (cleanup/defer), smart pointers become one tool among many.
- Opinions vary:
- Some say “just use C++ and
std::unique_ptr” instead of C hacks. - Others avoid C++ for complexity or control reasons, recreating OO patterns in C (X macros, header-based inheritance, virtual-function-like switches).
- Some say “just use C++ and
- Go’s function-scoped defer is criticized as inferior to scope-based RAII for preventing deadlocks and making lock lifetimes clear.
- Rust’s
Droptrait is praised conceptually but also noted to have limits (no error returns, no extra parameters). - Zig is cited as a language that bakes arena/allocator-style lifetime management into its standard approach.
Tooling and verification
- Some suggest that, for serious code, static/bounded model checkers (e.g., CBMC) are a better route to memory-safety assurance than deep stack hacks.