A closure in C is a function plus an environment
A callback and an explicit context pointer form a portable way to represent closure-like behavior in C. Keeping the environment visible makes its lifetime and calling convention part of the design.
Separate code from captured state
A closure is commonly understood as executable code paired with the environment it uses. ISO C already has function pointers, so a callback can represent the code. A separate object can hold the values that a nested function might otherwise capture, and a void * can carry a pointer to that object through a generic callback interface.
This division is useful because the function pointer alone does not carry state. The caller passes both pieces explicitly: one says what operation to invoke, and one says which environment the operation should use. The callback ABI is visible in the type, so an API can document what arguments it supplies and how the callback receives its context.
Trace the callback and context
In the example, Callback accepts a context pointer and an integer. Closure stores a Callback and a void * environment. The Offset object contains the captured amount, and add_offset interprets the environment as a pointer to that object before adding its amount to the input.
main initializes the offset to seven, stores its address in the closure, and invokes the callback with five. The callback reads seven from the environment and returns twelve; printf writes that decimal value followed by a newline. The context object lives in main's block and remains alive for the call. Saving this closure for use after main's local object has expired would leave the environment pointer dangling.
#include <stdio.h>
typedef int (*Callback)(void *, int);
struct Closure { Callback call; void *env; };
struct Offset { int amount; };
static int add_offset(void *env, int x) {
const struct Offset *offset = env;
return x + offset->amount;
}
int main(void) {
struct Offset offset = { 7 };
struct Closure closure = { add_offset, &offset };
printf("%d\n", closure.call(closure.env, 5));
return 0;
}
Lifetime is part of the representation
The explicit pointer makes ownership questions hard to miss. A synchronous API can accept a pointer to a stack object if it guarantees the callback finishes before the caller returns. An asynchronous queue cannot safely retain that pointer unless the object outlives the queued work. The environment might instead be heap allocated, embedded in a longer-lived owner, or copied by a documented API.
Each choice has a cost. Stack context is cheap and simple, but short-lived. Heap context can outlive the current call, but needs a clear release rule. Copying context can simplify ownership for small values, but may be unsuitable for large or identity-bearing state. The callback interface does not decide these policies; the API contract must.
Nested functions change the tradeoff
GCC supports nested functions as a GNU C extension. Such a function can refer to variables in its enclosing scope, and taking its address may require a trampoline that arranges access to the enclosing environment. GCC cautions that calling the nested function through its address after the containing function exits is unsafe when it refers to expired locals [1]. The extension does not extend those locals' lifetimes.
An explicit callback and context work across ordinary C implementations and make the lifetime relationship inspectable. They require a little more plumbing, and a generic void * context gives up some type checking at the boundary. That is often a useful exchange: the representation states exactly which code runs, which data it receives, and how long that data must remain valid.