Functions#

A function in C has a return type, a name, a parameter list, and a body. There are no closures, no methods, no overloading. Functions are first-class values (function pointers) and support variadic parameters through <stdarg.h>.

For function pointers used inside structs to fake methods, see OOP. For the call / return semantics around defer- style cleanup, see Control flow.

Declaration vs definition#

A declaration (a prototype) introduces the name and signature; a definition also provides the body. The operator puts prototypes in headers and definitions in source files.

/* in scan.h */
int probe(const char *host, int port);

/* in scan.c */
int probe(const char *host, int port) {
    /* … */
    return 0;
}

A function with no body but visible to other translation units is declared extern; the linker resolves it.

Parameters#

Parameters are typed and passed by value. To mutate caller state, pass a pointer.

void inc(int *p) { (*p)++; }

int x = 0;
inc(&x);                     /* x is now 1 */

Arrays decay to pointers when passed; the operator passes the length alongside.

void print(const int *xs, size_t n) {
    for (size_t i = 0; i < n; i++) printf("%d\n", xs[i]);
}

Strings are const char * when not modified; char * when the function writes into them.

Return values#

A function returns at most one value. Multiple results need an output pointer or a struct return.

int divmod(int a, int b, int *rem) {
    *rem = a % b;
    return a / b;
}

struct pair { int x, y; };
struct pair make_pair(int x, int y) {
    return (struct pair){.x = x, .y = y};
}

The classic convention: return an error code as the value; write results through pointer parameters.

int load(const char *path, char **out, size_t *out_n) {
    FILE *f = fopen(path, "rb");
    if (!f) return -1;
    /* … */
    *out = buf;
    *out_n = len;
    return 0;
}

static#

static on a function gives it internal linkage; only the current translation unit can call it. The operator marks helpers static by default; only deliberately-public functions omit static.

static int normalize(int x) { /* private to this .c */ return x; }
int  process(int x)         { return normalize(x); }

inline#

inline is a hint to the compiler. Modern compilers usually inline whenever profitable; write inline for trivial helpers in headers so the linker does not complain about multiple definitions.

/* in header */
static inline int max(int a, int b) { return a > b ? a : b; }

Variadic#

... declares a variadic parameter list; <stdarg.h>’s va_start / va_arg / va_end macros read it.

#include <stdarg.h>

void logf(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
}

logf("%s=%d\n", "port", 8080);

The variadic API has no type information; the caller and callee must agree on the format. printf family uses the format string as the source of truth.

Function pointers#

A function pointer holds the address of a function. The type mirrors the signature with a (*) around the name.

int  (*op)(int, int);            /* pointer to int(int,int) */
op = &add;
int z = op(1, 2);

typedef int (*op_t)(int, int);
op_t handler = &add;

Use function pointers for plugin tables, sort comparators, vtables (see OOP).

int cmp_int(const void *a, const void *b) {
    int x = *(const int*)a, y = *(const int*)b;
    return (x > y) - (x < y);
}

int xs[] = {3, 1, 4, 1, 5};
qsort(xs, 5, sizeof xs[0], cmp_int);

Recursion#

C supports recursion; there are no tail-call guarantees, so deep recursion can blow the stack. The operator rewrites deeply-recursive algorithms iteratively or with an explicit heap-allocated stack.

long factorial(long n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

Compound literals as call arguments#

C99+ lets the operator build an aggregate inline.

process(&(struct config){.host = "0.0.0.0", .port = 80});

The lifetime of the compound literal is the enclosing block; storing its address past the block is undefined behaviour.

__attribute__#

GCC / Clang attributes carry extra info to the compiler. The operator’s go-tos.

Attribute

Effect

__attribute__((noreturn))

function never returns. abort, exit, longjmp.

__attribute__((format(printf, 1, 2)))

validate printf-style arguments at the call site.

__attribute__((warn_unused_result))

warn if the caller ignores the return value.

__attribute__((cleanup(fn)))

call fn when the variable goes out of scope (GCC-only).

C23 standardises some of these as [[noreturn]], [[deprecated]], [[nodiscard]].

References#

  • Syntax for the lexical surface T name(...) parses against.

  • Types for parameter / return types and the decay rule.

  • OOP for function-pointer-in-struct vtables.

  • Errors for the return-code convention and setjmp.

  • Function declarations.