22. Variadic functions and _Generic
22.1 Variadic functions (printf's mechanism)
#include <stdio.h>
#include <stdarg.h>
double sum(int count, ...) {
va_list args;
va_start(args, count); // must start right after the last named parameter
double total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, double); // caller and callee MUST agree on the type here —
// there is no runtime check
}
va_end(args);
return total;
}
int main(void) {
printf("%.1f\n", sum(3, 1.0, 2.0, 3.0)); // 6.0
return 0;
}
Because the compiler cannot check that the arguments you pass match the
types va_arg requests, variadic functions are the other classic UB
source in C after strings — this is exactly the mechanism behind Section
1.4's format-string mismatch bug.
22.2 _Generic: compile-time dispatch by type (C11)
#include <stdio.h>
#define print_value(x) _Generic((x), \
int: print_int, \
double: print_double, \
char *: print_string \
)(x)
void print_int(int x) { printf("int: %d\n", x); }
void print_double(double x) { printf("double: %f\n", x); }
void print_string(char *x) { printf("string: %s\n", x); }
int main(void) {
print_value(42); // "int: 42" — resolved at COMPILE time, zero runtime cost
print_value(3.14); // "double: 3.140000"
print_value("hi"); // "string: hi"
return 0;
}
_Generic is how <tgmath.h>'s sqrt/sin/cos pick the float vs.
double vs. long double variant, and it's the closest C gets to
function overloading — but it's resolved entirely at compile time by the
static type of the expression, never at runtime, which is why it can't
replace a real void*-based runtime-polymorphic API like qsort's.