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 defer for 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).
  • Go’s function-scoped defer is criticized as inferior to scope-based RAII for preventing deadlocks and making lock lifetimes clear.
  • Rust’s Drop trait 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.