Variadic Functions
Master variadic functions with variable arguments. Learn va_list, va_start, va_arg, va_end, type-safe approaches, common patterns, and how printf/scanf work. Build flexible APIs accepting any number of arguments.
Understanding Variadic Functions
Variadic functions accept variable numbers of arguments. printf() and scanf() are famous examples. Use stdarg.h macros: va_list holds arguments, va_start initializes, va_arg retrieves next, va_end cleans up. No type safety - caller must know argument types.
#include <stdio.h>
#include <stdarg.h>
/* Basic variadic function */
void print_ints(int count, ...) {
va_list args; /* Argument list */
va_start(args, count); /* Initialize with last named parameter */
for (int i = 0; i < count; i++) {
int value = va_arg(args, int); /* Get next int */
printf("%d ", value);
}
va_end(args); /* Cleanup */
printf("\n");
}
/* Usage */
void variadic_example(void) {
print_ints(3, 10, 20, 30); /* 10 20 30 */
print_ints(5, 1, 2, 3, 4, 5); /* 1 2 3 4 5 */
}
/* How printf works (simplified) */
int my_printf(const char *format, ...) {
va_list args;
va_start(args, format);
int count = 0;
for (const char *p = format; *p; p++) {
if (*p == '%') {
p++; /* Skip % */
switch (*p) {
case 'd': {
int i = va_arg(args, int);
/* Print integer */
count += printf("%d", i);
break;
}
case 's': {
char *s = va_arg(args, char*);
/* Print string */
count += printf("%s", s);
break;
}
case 'f': {
double f = va_arg(args, double);
/* Print float */
count += printf("%f", f);
break;
}
/* More format specifiers... */
}
} else {
putchar(*p);
count++;
}
}
va_end(args);
return count;
}
/* Requirements for variadic functions */
/*
1. At least one named parameter
2. Named parameter must come before ...
3. No type checking
4. Caller must know argument count/types
5. Default argument promotions apply:
- char/short promoted to int
- float promoted to double
*/
/* Syntax */
void func(int fixed, ...); /* ... means variadic */
/* Invalid: No fixed parameter */
// void bad(...); /* Error */
/* Invalid: ... must be last */
// void bad2(..., int x); /* Error */Stdarg Macros
stdarg.h provides va_list, va_start, va_arg, va_end, va_copy. Understanding these macros is essential for variadic functions. Order matters - call in sequence. Clean up with va_end prevents leaks.
#include <stdarg.h>
/* va_list - holds argument list */
va_list args;
/* va_start - initialize va_list */
va_start(args, last_named_param);
/* va_arg - get next argument */
int i = va_arg(args, int);
char *s = va_arg(args, char*);
double d = va_arg(args, double);
/* va_end - cleanup */
va_end(args);
/* va_copy - copy va_list (C99) */
va_list args2;
va_copy(args2, args);
/* Use args2 */
va_end(args2);
/* Complete example */
double sum_doubles(int count, ...) {
va_list args;
va_start(args, count);
double total = 0.0;
for (int i = 0; i < count; i++) {
double value = va_arg(args, double);
total += value;
}
va_end(args);
return total;
}
/* Usage */
double s = sum_doubles(4, 1.5, 2.5, 3.5, 4.5); /* 12.0 */
/* Multiple passes with va_copy */
void print_twice(const char *format, ...) {
va_list args, args_copy;
va_start(args, format);
va_copy(args_copy, args); /* Copy for second pass */
/* First pass */
vprintf(format, args);
/* Second pass */
vprintf(format, args_copy);
va_end(args_copy);
va_end(args);
}
/* Passing va_list to other functions */
void vlog_message(const char *format, va_list args) {
printf("[LOG] ");
vprintf(format, args);
printf("\n");
}
void log_message(const char *format, ...) {
va_list args;
va_start(args, format);
vlog_message(format, args); /* Forward va_list */
va_end(args);
}
/* v-versions of standard functions */
/*
vprintf(format, va_list)
vsprintf(buffer, format, va_list)
vsnprintf(buffer, size, format, va_list)
vfprintf(file, format, va_list)
vscanf(format, va_list)
vsscanf(string, format, va_list)
*/
/* Safe wrapper example */
void error_log(const char *format, ...) {
va_list args;
va_start(args, format);
fprintf(stderr, "ERROR: ");
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
va_end(args);
}
/* Usage */
error_log("File not found: %s", filename);
error_log("Invalid value: %d (expected %d)", actual, expected);
/* Type promotion rules */
void promotion_example(void) {
va_list args;
/* char promoted to int */
/* short promoted to int */
/* float promoted to double */
/* Always use promoted type */
// char c = va_arg(args, char); /* WRONG */
int c = va_arg(args, int); /* RIGHT */
// float f = va_arg(args, float); /* WRONG */
double f = va_arg(args, double); /* RIGHT */
}Common Variadic Patterns
Several patterns communicate argument count/types: explicit count, sentinel value, format string, tagged arguments. Each has trade-offs. Choose based on use case. Format strings most flexible, sentinels simple, counts fastest.
/* Pattern 1: Explicit count */
int sum(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
sum(3, 10, 20, 30); /* Count first */
/* Pattern 2: Sentinel value */
int sum_sentinel(int first, ...) {
va_list args;
va_start(args, first);
int total = first;
int value;
while ((value = va_arg(args, int)) != 0) { /* 0 = end */
total += value;
}
va_end(args);
return total;
}
sum_sentinel(10, 20, 30, 0); /* 0 terminates */
/* Pattern 3: Format string (like printf) */
void log_values(const char *format, ...) {
va_list args;
va_start(args, format);
for (const char *p = format; *p; p++) {
switch (*p) {
case 'i': {
int i = va_arg(args, int);
printf("%d ", i);
break;
}
case 's': {
char *s = va_arg(args, char*);
printf("%s ", s);
break;
}
case 'f': {
double f = va_arg(args, double);
printf("%f ", f);
break;
}
}
}
va_end(args);
printf("\n");
}
log_values("isi", 42, "hello", 30); /* Format describes args */
/* Pattern 4: Tagged arguments */
typedef enum {
ARG_END,
ARG_INT,
ARG_STRING,
ARG_DOUBLE
} ArgType;
/* Standard C (before C23) requires at least one named parameter */
void print_args(ArgType first_type, ...) {
va_list args;
va_start(args, first_type);
ArgType type = first_type;
while (type != ARG_END) {
switch (type) {
case ARG_INT: {
int i = va_arg(args, int);
printf("%d ", i);
break;
}
case ARG_STRING: {
char *s = va_arg(args, char*);
printf("%s ", s);
break;
}
case ARG_DOUBLE: {
double d = va_arg(args, double);
printf("%f ", d);
break;
}
case ARG_END:
break;
}
type = va_arg(args, ArgType);
}
va_end(args);
printf("\n");
}
print_args(ARG_INT, 42, ARG_STRING, "test", ARG_END);
/* Min/Max functions */
int min(int count, ...) {
va_list args;
va_start(args, count);
int minimum = va_arg(args, int);
for (int i = 1; i < count; i++) {
int value = va_arg(args, int);
if (value < minimum) {
minimum = value;
}
}
va_end(args);
return minimum;
}
int m = min(5, 30, 10, 50, 20, 40); /* 10 */
/* String concatenation */
char* concat(int count, ...) {
va_list args;
va_start(args, count);
/* Calculate total length */
size_t total_len = 0;
va_list args_copy;
va_copy(args_copy, args);
for (int i = 0; i < count; i++) {
const char *s = va_arg(args_copy, const char*);
total_len += strlen(s);
}
va_end(args_copy);
/* Allocate */
char *result = malloc(total_len + 1);
if (result == NULL) {
va_end(args);
return NULL;
}
/* Concatenate */
result[0] = '\0';
for (int i = 0; i < count; i++) {
const char *s = va_arg(args, const char*);
strcat(result, s);
}
va_end(args);
return result;
}
char *str = concat(3, "Hello", " ", "World"); /* "Hello World" */
free(str);
/* Building command line */
char** build_args(int count, ...) {
va_list args;
va_start(args, count);
char **argv = malloc((count + 1) * sizeof(char*));
if (argv == NULL) {
va_end(args);
return NULL;
}
for (int i = 0; i < count; i++) {
const char *arg = va_arg(args, const char*);
argv[i] = strdup(arg);
}
argv[count] = NULL; /* Null-terminate */
va_end(args);
return argv;
}Type Safety and Error Handling
Variadic functions lack type safety - wrong types cause undefined behavior. Use format strings for type information. Validate arguments when possible. Consider alternatives: arrays, structures, or function pointers for type-safe variable arguments.
/* Problem: No type safety */
void unsafe(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
int x = va_arg(args, int); /* Expects int */
printf("%d ", x);
}
va_end(args);
}
unsafe(2, 10, 20); /* OK */
unsafe(2, "hello", "world"); /* UNDEFINED BEHAVIOR! */
/* Solution 1: Format string */
void type_safe(const char *format, ...) {
va_list args;
va_start(args, format);
for (const char *p = format; *p; p++) {
if (*p == 'd') {
int i = va_arg(args, int);
printf("%d ", i);
} else if (*p == 's') {
char *s = va_arg(args, char*);
printf("%s ", s);
}
}
va_end(args);
}
type_safe("dd", 10, 20); /* OK */
type_safe("ss", "hi", "bye"); /* OK */
/* Solution 2: Tagged union */
typedef enum { TYPE_INT, TYPE_STRING, TYPE_DOUBLE } ValueType;
typedef struct {
ValueType type;
union {
int i;
char *s;
double d;
} data;
} Value;
void print_values_safe(int count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
Value v = va_arg(args, Value);
switch (v.type) {
case TYPE_INT:
printf("%d ", v.data.i);
break;
case TYPE_STRING:
printf("%s ", v.data.s);
break;
case TYPE_DOUBLE:
printf("%f ", v.data.d);
break;
}
}
va_end(args);
}
Value v1 = {TYPE_INT, {.i = 42}};
Value v2 = {TYPE_STRING, {.s = "hello"}};
print_values_safe(2, v1, v2); /* Type-safe */
/* Solution 3: Array + count */
void print_ints_array(const int *values, int count) {
for (int i = 0; i < count; i++) {
printf("%d ", values[i]);
}
}
int arr[] = {10, 20, 30};
print_ints_array(arr, 3); /* Type-safe */
/* Solution 4: Null-terminated array */
void print_strings(const char **strings) {
while (*strings != NULL) {
printf("%s ", *strings);
strings++;
}
}
const char *strs[] = {"hello", "world", NULL};
print_strings(strs); /* Type-safe */
/* GCC format checking */
__attribute__((format(printf, 1, 2)))
void my_printf(const char *format, ...);
/* Wrong usage caught by compiler */
// my_printf("%d", "string"); /* Warning! */
/* Custom format checking */
#define CHECK_FORMAT __attribute__((format(printf, 1, 2)))
CHECK_FORMAT
void log_msg(const char *format, ...) {
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
}
/* Error handling */
int safe_sum(int count, ...) {
if (count < 0) {
return -1; /* Invalid count */
}
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
int value = va_arg(args, int);
/* Check overflow */
if (total > INT_MAX - value) {
va_end(args);
return -1; /* Overflow */
}
total += value;
}
va_end(args);
return total;
}
/* When NOT to use variadic functions */
/*
Avoid when:
- Type safety critical
- Performance critical
- Simple API possible
- Fixed arguments work
Use when:
- Printf-like formatting needed
- Truly variable arguments
- Convenience outweighs safety
- Backward compatibility required
*/Advanced Techniques
Advanced variadic patterns include wrappers around printf/scanf, building DSLs, dynamic function calls. Understanding these techniques enables powerful metaprogramming and flexible APIs.
/* Printf wrapper with timestamp */
void tprintf(const char *format, ...) {
time_t now = time(NULL);
printf("[%s] ", ctime(&now));
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
}
/* Logging levels */
typedef enum { LOG_ERROR, LOG_WARN, LOG_INFO, LOG_DEBUG } LogLevel;
void log_msg(LogLevel level, const char *format, ...) {
const char *level_str[] = {"ERROR", "WARN", "INFO", "DEBUG"};
printf("[%s] ", level_str[level]);
va_list args;
va_start(args, format);
vprintf(format, args);
va_end(args);
printf("\n");
}
log_msg(LOG_ERROR, "Failed to open %s", filename);
log_msg(LOG_INFO, "Connected to %s:%d", host, port);
/* SQL-like query builder */
void execute_query(const char *sql, ...) {
va_list args;
va_start(args, sql);
/* Build query with parameters */
char query[1024];
vsnprintf(query, sizeof(query), sql, args);
va_end(args);
/* Execute query */
printf("Executing: %s\n", query);
}
execute_query("SELECT * FROM users WHERE id = %d", 42);
execute_query("INSERT INTO logs VALUES ('%s', %d)", "event", time(NULL));
/* Function call dispatcher */
typedef void (*Handler)(va_list args);
void dispatch(Handler handler, ...) {
va_list args;
va_start(args, handler);
handler(args); /* Forward to handler */
va_end(args);
}
void int_handler(va_list args) {
int a = va_arg(args, int);
int b = va_arg(args, int);
printf("Sum: %d\n", a + b);
}
dispatch(int_handler, 10, 20);
/* Building JSON (simplified) */
void json_object(int count, ...) {
printf("{");
va_list args;
va_start(args, count);
for (int i = 0; i < count; i++) {
const char *key = va_arg(args, const char*);
const char *value = va_arg(args, const char*);
printf("\"%s\": \"%s\"", key, value);
if (i < count - 1) {
printf(", ");
}
}
va_end(args);
printf("}\n");
}
json_object(2, "name", "John", "age", "30");
/* {"name": "John", "age": "30"} */
/* Configuration builder */
typedef struct Config Config;
Config* config_new(const char *key, ...) {
Config *cfg = malloc(sizeof(Config));
if (cfg == NULL) {
return NULL;
}
va_list args;
va_start(args, key);
const char *k = key;
while (k != NULL) {
const char *value = va_arg(args, const char*);
/* Set config[k] = value */
k = va_arg(args, const char*);
}
va_end(args);
return cfg;
}
Config *cfg = config_new("host", "localhost",
"port", "8080",
NULL);
/* Test framework */
void run_tests(int count, ...) {
va_list args;
va_start(args, count);
int passed = 0;
for (int i = 0; i < count; i++) {
void (*test)(void) = va_arg(args, void (*)(void));
printf("Running test %d... ", i + 1);
test();
printf("PASSED\n");
passed++;
}
va_end(args);
printf("%d/%d tests passed\n", passed, count);
}
void test1(void) { /* test */ }
void test2(void) { /* test */ }
run_tests(2, test1, test2);Summary & What's Next
Key Takeaways:
- ✅ Variadic functions accept variable arguments
- ✅ Use va_list, va_start, va_arg, va_end
- ✅ Must have at least one named parameter
- ✅ No type checking - caller responsibility
- ✅ Use format strings to communicate types
- ✅ va_copy for multiple passes (C99)
- ✅ Prefer type-safe alternatives when possible
- ✅ Use v-versions to forward va_list