HN Debrief

Using GCC's Nested Functions with Wide Pointers and No Trampolines II

  • Programming
  • Compilers
  • Developer Tools
  • Open Source

The post is about GCC nested functions, which let a function defined inside another function read the parent’s local variables. Traditional GCC implements captured nested functions by building a tiny helper stub called a trampoline that sets up the hidden environment pointer and then jumps into the real code. That works, but it usually means executable stack memory and it blocks some compiler optimizations. The new approach packages the code pointer together with its environment as a wider function reference instead of pretending every callback is a plain C function pointer.

If you own a C API, keep passing an explicit context pointer because it stays portable and predictable. If GCC or future C standards adopt closure-like function references, the payoff is less boilerplate and better type safety around callbacks, not just a security fix.

Discussion mood

Interested but pragmatic. People liked the attempt to get rid of executable-stack trampolines and many clearly enjoy nested functions in other languages, but they kept pulling the conversation back to everyday C practice: explicit context pointers, API design, and whether the new mechanism solves enough real problems to justify the complexity.

Key insights

  1. 01

    Nested functions mainly replace callback boilerplate

    The real win is not magical new expressiveness. It is eliminating the manual pattern where callers build a struct, pack locals into it, pass a void pointer through an API, then unpack it inside the callback. That matters most when a callback needs several pieces of state, because the usual C workaround scales badly and weakens type safety at exactly the point where bugs hide.

    When you review callback-heavy C code, treat nested-function support as a way to collapse hand-rolled closure structs. If your callbacks regularly need more than one local variable, that is where a closure-like feature starts paying for itself.

      Attribution:
    • mananaysiempre #1
    • sltkr #1
    • uecker #1
  2. 02

    Inline lambdas and local functions solve different problems

    What people call a lambda often bundles two separate features. One is writing a function body directly where it is used. The other is capturing local state. GCC nested functions only partially solve the first problem because declarations still pull code out of order, while C++ lambdas solve the placement problem well but are usually closure objects rather than plain functions. That distinction explains both the syntax differences and the ABI differences.

    Be precise when discussing callback features in a language or API. Decide whether you need inline function expressions, state capture, or compatibility with plain function pointers, because one mechanism rarely gives you all three cleanly.

      Attribution:
    • wasmperson #1
    • mananaysiempre #1
    • sltkr #1
    • gpderetta #1
  3. 03

    Trampolines hurt optimization as much as security

    The executable-stack objection got most of the attention, but the more durable compiler argument is that trampolines erase information the optimizer wants. Once a nested function becomes an indirect jump through a generated stub, it is much harder to devirtualize or reason about than a first-class pair of code pointer plus environment. That is why the old tagged-pointer patch and the new wide-pointer approach are framed as performance and compiler-IR improvements, not just hardening work.

    If you evaluate closure-like features in low-level languages, check what representation reaches the optimizer. A design that preserves the function-and-environment pair explicitly is more likely to inline well than one that lowers early to an opaque function pointer.

      Attribution:
    • jcranmer #1
    • uecker #1 #2
  4. 04

    Modules already cover part of the encapsulation argument

    Keeping helper functions out of the global namespace sounds compelling, but C already handles much of that with file-local static functions and ordinary module boundaries. The older Hanson paper cited here goes further and argues that proper modules beat full block-structured nested routines for systems languages because they simplify both implementation and runtime machinery. That does not kill nested functions, but it narrows their justification. Their strongest case is captured local state, not mere visibility control.

    Do not sell local functions inside a systems language primarily as an encapsulation feature. If your real need is hiding symbols, module boundaries and static functions are simpler tools.

      Attribution:
    • Joker_vD #1
    • kccqzy #1

Against the grain

  1. 01

    Context-aware APIs already make this less compelling

    If you are already using qsort_r or any callback API that accepts user data, the wide-pointer approach can look like syntax sugar over passing a target pointer directly. In the simple one-variable case, it does not buy much. The value only appears when the captured environment is large enough that building the context struct becomes annoying.

    Estimate the actual boilerplate before adopting any compiler-specific callback feature. For simple callbacks with one piece of state, a normal context pointer may still be the clearest choice.

      Attribution:
    • listeria #1
  2. 02

    Forbidding function-pointer conversion may be cleaner

    One pushback was that C++ avoided the trampoline mess by not letting capturing lambdas turn into plain function pointers in the first place. That forces programmers to acknowledge when they are dealing with a closure-like object instead of a raw function. It is a stricter model, but it keeps the ABI honest and avoids papering over hidden state with ad hoc runtime machinery.

    Be wary of features that pretend captured callbacks are ordinary C function pointers. Keeping closure-like values distinct in the type system often leads to cleaner APIs and fewer ABI surprises.

      Attribution:
    • kccqzy #1

In plain english

ABI
Application Binary Interface, the low-level calling convention and data representation rules used when compiled code interacts at runtime.
closure
A function value bundled with the surrounding variables it captures so it can use them later.
devirtualize
An optimization where the compiler figures out the exact target of an indirect call and turns it into a direct call.
GCC
GNU Compiler Collection, a major open-source compiler suite for C, C++, and other languages.
Lambda
AWS Lambda, Amazon's serverless function service that runs code on demand.
qsort
The standard C library function for generic array sorting using a user-provided comparison callback.
qsort_r
A non-standard variant of qsort that also passes a user-supplied context pointer to the comparison callback.
RAII
Resource Acquisition Is Initialization, a C++ technique where resource management is tied to object lifetime.
static chain
A hidden pointer passed to a nested function so it can access variables from its enclosing function's stack frame.
std::function
A C++ standard library type-erased callable wrapper that can store many kinds of functions, lambdas, or function objects.
trampoline
A small helper code stub that sets up state and then jumps to another function, often used to adapt one calling convention to another.
wide pointer
A pointer-like value that carries extra data in addition to an address, here meaning a function reference plus its captured environment.

Reference links

Author references and demos

Background on trampolines and closures

Historical and language-design references

Codebase example