Skip to content
C Basics: Pass State to a Callback Without Global Variables

C Basics: Pass State to a Callback Without Global Variables

November 1, 2026

A reusable traversal should not decide where callers keep their totals. Pass a callback and its state explicitly instead of depending on a global variable.

Prepare a scratch directory

Tested on Linux amd64 with GCC 14.2.0. You need GCC with C17 support, Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. No external library is required.

Keep the same terminal open. Stop if setup fails, before saving the source:

start_directory="$PWD"
work_directory="$(mktemp -d)" || exit 1
cd -- "$work_directory" || exit 1

Run the example

Save this as callback-context-demo.c:

#include <stddef.h>
#include <stdio.h>

typedef void (*value_callback)(int value, void *context);
struct summary { int total; size_t count; };

static void visit_values(const int *values, size_t count,
                         value_callback callback, void *context) {
    for (size_t index = 0; index < count; ++index) {
        callback(values[index], context);
    }
}

static void collect_value(int value, void *context) {
    struct summary *summary = context;
    summary->total += value;
    ++summary->count;
}

int main(void) {
    const int values[] = { 3, 5, 7 };
    struct summary result = { .total = 0, .count = 0 };
    visit_values(values, sizeof values / sizeof values[0],
                 collect_value, &result);
    printf("Count: %zu\n", result.count);
    printf("Total: %d\n", result.total);
    return 0;
}

Compile and run it. The program runs only if compilation succeeds:

gcc -std=c17 -Wall -Wextra -Werror -pedantic callback-context-demo.c -o callback-context-demo &&
./callback-context-demo

Observed output:

Count: 3
Total: 15

Read the result

The traversal invokes a compatible function pointer. The callback converts the context back to the object type supplied by the caller. State stays local to main.

Apply it carefully

The callback and context must agree on the pointed-to type, and the array, callback and context must remain valid throughout the visit. This function calls synchronously and retains nothing. An asynchronous API needs an explicit lifetime contract. The example values fit in int; production totals need overflow handling.

See the WG14 C draft, sections 6.3.2.3 and 6.5.2.2 for the underlying language rules.

Clean up

Remove only the source and executable created here, return to the starting directory, and remove the empty practice directory:

rm -- callback-context-demo.c callback-context-demo
cd -- "$start_directory" || exit 1
rmdir -- "$work_directory"
unset start_directory work_directory

If the directory is not empty, inspect its contents before removing anything else.

Acknowledgements

This post was created with assistance from AI.

Last updated on