Docs

README

Variable Arguments (Variadic Functions) in C

Table of Contents

  1. •Introduction
  2. •The stdarg.h Header
  3. •How Variadic Functions Work
  4. •Basic Variadic Function Structure
  5. •va_list, va_start, va_arg, va_end
  6. •va_copy Macro
  7. •Determining Argument Count
  8. •Type Safety Considerations
  9. •Common Patterns
  10. •Implementing printf-like Functions
  11. •Variadic Macros
  12. •Best Practices
  13. •Common Pitfalls
  14. •Real-World Examples
  15. •Summary

Introduction

Variable argument functions (also called variadic functions) are functions that accept a variable number of arguments. This is a powerful feature in C that allows creating flexible functions like printf(), scanf(), and many utility functions.

Why Use Variadic Functions?

  1. •Flexibility: Accept any number of arguments
  2. •Convenience: Single function for multiple use cases
  3. •Code Reuse: Avoid writing multiple overloaded functions
  4. •Standard Library: Many C functions use this feature

Common Examples in Standard Library

/* printf - variable number of arguments for formatting */
printf("Hello %s, you are %d years old\n", name, age);

/* scanf - variable number of arguments for input */
scanf("%d %f", &integer, &floating);

/* fprintf - similar to printf with file stream */
fprintf(stderr, "Error: %s (code %d)\n", message, code);

/* sprintf - format into string */
sprintf(buffer, "Result: %d", value);

The stdarg.h Header

The <stdarg.h> header provides macros for working with variable arguments.

Components of stdarg.h

ComponentType/Description
va_listType for holding information about variable arguments
va_startMacro to initialize va_list
va_argMacro to retrieve the next argument
va_endMacro to clean up va_list
va_copyMacro to copy va_list (C99)

Basic Include

#include <stdarg.h>

/* Now you can use:
 * - va_list
 * - va_start
 * - va_arg
 * - va_end
 * - va_copy (C99)
 */

How Variadic Functions Work

Function Declaration Syntax

/* General syntax */
return_type function_name(fixed_params, ...);

/* Examples */
int sum(int count, ...);                     /* Count-based */
void print_strings(const char *first, ...); /* Sentinel-based */
int printf(const char *format, ...);        /* Format-based */

The Ellipsis (...)

  • •The ... indicates variable arguments
  • •Must appear at the end of the parameter list
  • •Requires at least one fixed (named) parameter before it

Memory Layout

When a variadic function is called, arguments are typically pushed onto the stack:

Higher addresses
+------------------+
|  Last vararg     |
+------------------+
|  ...             |
+------------------+
|  First vararg    |
+------------------+
|  Last fixed arg  |  <- va_list starts after this
+------------------+
|  ...             |
+------------------+
|  First fixed arg |
+------------------+
|  Return address  |
+------------------+
Lower addresses

Basic Variadic Function Structure

Template

#include <stdarg.h>

return_type function_name(fixed_param, ...) {
    va_list args;           /* 1. Declare va_list */

    va_start(args, fixed_param);  /* 2. Initialize with last fixed param */

    /* 3. Process arguments using va_arg */
    type value = va_arg(args, type);

    va_end(args);           /* 4. Clean up */

    return result;
}

Simple Example: Sum of Integers

#include <stdio.h>
#include <stdarg.h>

/* Sum a variable number of integers */
int sum(int count, ...) {
    va_list args;
    int total = 0;

    va_start(args, count);

    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);
    }

    va_end(args);

    return total;
}

int main(void) {
    printf("Sum: %d\n", sum(3, 10, 20, 30));     /* 60 */
    printf("Sum: %d\n", sum(5, 1, 2, 3, 4, 5));  /* 15 */
    return 0;
}

va_list, va_start, va_arg, va_end

va_list

A type that holds information needed to retrieve additional arguments.

va_list args;  /* Declare a variable of type va_list */

va_start

Initializes the va_list to point to the first variable argument.

/* Syntax */
void va_start(va_list ap, last_fixed_param);

/* Example */
void my_func(int x, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);  /* Initialize after 'fmt' */
    /* ... */
    va_end(args);
}

Important: The second parameter must be the name of the last fixed parameter.

va_arg

Retrieves the next argument of the specified type.

/* Syntax */
type va_arg(va_list ap, type);

/* Examples */
int i = va_arg(args, int);
double d = va_arg(args, double);
char *s = va_arg(args, char *);

Type Promotion Rules:

  • •char and short are promoted to int
  • •float is promoted to double
/* WRONG - char is promoted to int */
char c = va_arg(args, char);

/* CORRECT */
char c = (char)va_arg(args, int);

/* WRONG - float is promoted to double */
float f = va_arg(args, float);

/* CORRECT */
float f = (float)va_arg(args, double);

va_end

Cleans up the va_list. Must be called before the function returns.

/* Syntax */
void va_end(va_list ap);

/* Example */
void my_func(int count, ...) {
    va_list args;
    va_start(args, count);

    /* Process arguments */

    va_end(args);  /* Clean up - REQUIRED */
}

va_copy Macro

The va_copy macro (C99) creates a copy of a va_list. This is useful when you need to process arguments multiple times.

Syntax

void va_copy(va_list dest, va_list src);

Usage Example

#include <stdio.h>
#include <stdarg.h>

void process_twice(int count, ...) {
    va_list args1, args2;

    va_start(args1, count);
    va_copy(args2, args1);  /* Create a copy */

    /* First pass: calculate sum */
    int sum = 0;
    for (int i = 0; i < count; i++) {
        sum += va_arg(args1, int);
    }
    printf("Sum: %d\n", sum);

    /* Second pass: print each value */
    printf("Values: ");
    for (int i = 0; i < count; i++) {
        printf("%d ", va_arg(args2, int));
    }
    printf("\n");

    va_end(args1);
    va_end(args2);  /* Must end both! */
}

int main(void) {
    process_twice(4, 10, 20, 30, 40);
    return 0;
}

Determining Argument Count

Since variadic functions don't automatically know how many arguments were passed, you need a way to determine the count.

Method 1: Explicit Count Parameter

int sum(int count, ...) {
    va_list args;
    int total = 0;

    va_start(args, count);
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);
    }
    va_end(args);

    return total;
}

/* Usage */
sum(3, 10, 20, 30);

Method 2: Sentinel Value

Use a special value to mark the end of arguments.

int sum_until_zero(..., 0) {
    va_list args;
    int total = 0;
    int val;

    va_start(args, /* first arg */);
    while ((val = va_arg(args, int)) != 0) {
        total += val;
    }
    va_end(args);

    return total;
}

/* Better: First argument is not variadic */
int sum_until_zero(int first, ...) {
    va_list args;
    int total = first;
    int val;

    if (first == 0) return 0;

    va_start(args, first);
    while ((val = va_arg(args, int)) != 0) {
        total += val;
    }
    va_end(args);

    return total;
}

/* Usage */
sum_until_zero(10, 20, 30, 0);  /* 0 is sentinel */

Method 3: Format String (like printf)

void my_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);

    for (const char *p = format; *p; p++) {
        if (*p == '%' && *(p+1)) {
            p++;
            switch (*p) {
                case 'd':
                    printf("%d", va_arg(args, int));
                    break;
                case 's':
                    printf("%s", va_arg(args, char*));
                    break;
                case 'f':
                    printf("%f", va_arg(args, double));
                    break;
                case '%':
                    putchar('%');
                    break;
            }
        } else {
            putchar(*p);
        }
    }

    va_end(args);
}

Method 4: NULL-Terminated Pointer List

void print_strings(const char *first, ...) {
    va_list args;
    const char *str;

    va_start(args, first);

    /* Print first string */
    printf("%s", first);

    /* Print remaining strings until NULL */
    while ((str = va_arg(args, const char*)) != NULL) {
        printf(" %s", str);
    }
    printf("\n");

    va_end(args);
}

/* Usage - NULL terminates the list */
print_strings("Hello", "World", "!", NULL);

Type Safety Considerations

The Problem

Variadic functions have no type checking for variable arguments:

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);  /* Assumes int! */
    }

    va_end(args);
    return total;
}

/* This compiles but causes undefined behavior! */
sum(3, 10, 20.5, "hello");  /* Wrong types */

Solutions

1. Document Expected Types

/**
 * Calculate sum of integers
 * @param count Number of integers following
 * @param ... Integers to sum (must be int type)
 */
int sum(int count, ...);

2. Use Type-Indicating Parameters

typedef enum { T_INT, T_DOUBLE, T_STRING, T_END } ArgType;

void print_values(ArgType type, ...) {
    va_list args;
    va_start(args, type);

    while (type != T_END) {
        switch (type) {
            case T_INT:
                printf("%d ", va_arg(args, int));
                break;
            case T_DOUBLE:
                printf("%f ", va_arg(args, double));
                break;
            case T_STRING:
                printf("%s ", va_arg(args, char*));
                break;
            default:
                break;
        }
        type = va_arg(args, ArgType);
    }

    va_end(args);
    printf("\n");
}

/* Usage */
print_values(T_INT, 42, T_DOUBLE, 3.14, T_STRING, "hello", T_END);

3. GCC format Attribute

/* Tell compiler this is printf-like */
__attribute__((format(printf, 1, 2)))
void my_printf(const char *fmt, ...);

/* Compiler will now warn about type mismatches */

Common Patterns

Pattern 1: Sum/Average of Numbers

double average(int count, ...) {
    va_list args;
    double sum = 0.0;

    va_start(args, count);
    for (int i = 0; i < count; i++) {
        sum += va_arg(args, double);
    }
    va_end(args);

    return count > 0 ? sum / count : 0.0;
}

Pattern 2: Maximum/Minimum

int max(int count, ...) {
    va_list args;
    va_start(args, count);

    int max_val = va_arg(args, int);  /* First value */

    for (int i = 1; i < count; i++) {
        int val = va_arg(args, int);
        if (val > max_val) {
            max_val = val;
        }
    }

    va_end(args);
    return max_val;
}

Pattern 3: String Concatenation

char *concat(const char *first, ...) {
    va_list args;
    size_t total_len = 0;

    /* First pass: calculate total length */
    va_start(args, first);
    for (const char *s = first; s != NULL; s = va_arg(args, const char*)) {
        total_len += strlen(s);
    }
    va_end(args);

    /* Allocate result buffer */
    char *result = malloc(total_len + 1);
    if (!result) return NULL;
    result[0] = '\0';

    /* Second pass: concatenate strings */
    va_start(args, first);
    for (const char *s = first; s != NULL; s = va_arg(args, const char*)) {
        strcat(result, s);
    }
    va_end(args);

    return result;
}

/* Usage */
char *str = concat("Hello", " ", "World", "!", NULL);
free(str);

Pattern 4: Array Construction

int *make_array(size_t *out_size, ...) {
    va_list args, args_copy;
    va_start(args, out_size);
    va_copy(args_copy, args);

    /* Count elements (until -1 sentinel) */
    size_t count = 0;
    while (va_arg(args, int) != -1) {
        count++;
    }
    va_end(args);

    /* Allocate and fill array */
    int *arr = malloc(count * sizeof(int));
    if (!arr) {
        va_end(args_copy);
        return NULL;
    }

    for (size_t i = 0; i < count; i++) {
        arr[i] = va_arg(args_copy, int);
    }
    va_end(args_copy);

    *out_size = count;
    return arr;
}

/* Usage */
size_t size;
int *arr = make_array(&size, 10, 20, 30, 40, -1);

Implementing printf-like Functions

Basic Custom Printf

#include <stdio.h>
#include <stdarg.h>

void my_printf(const char *format, ...) {
    va_list args;
    va_start(args, format);

    for (const char *p = format; *p != '\0'; p++) {
        if (*p != '%') {
            putchar(*p);
            continue;
        }

        /* Handle format specifier */
        p++;  /* Skip '%' */

        switch (*p) {
            case 'd':
            case 'i':
                printf("%d", va_arg(args, int));
                break;

            case 'u':
                printf("%u", va_arg(args, unsigned int));
                break;

            case 'f':
                printf("%f", va_arg(args, double));
                break;

            case 's': {
                char *s = va_arg(args, char*);
                printf("%s", s ? s : "(null)");
                break;
            }

            case 'c':
                putchar(va_arg(args, int));  /* char promoted to int */
                break;

            case 'p':
                printf("%p", va_arg(args, void*));
                break;

            case 'x':
                printf("%x", va_arg(args, unsigned int));
                break;

            case 'X':
                printf("%X", va_arg(args, unsigned int));
                break;

            case '%':
                putchar('%');
                break;

            default:
                putchar('%');
                putchar(*p);
                break;
        }
    }

    va_end(args);
}

Using vprintf, vfprintf, vsprintf

C provides v versions of printf functions that accept va_list:

#include <stdio.h>
#include <stdarg.h>

/* Custom error logging function */
void error_log(const char *format, ...) {
    va_list args;
    va_start(args, format);

    fprintf(stderr, "[ERROR] ");
    vfprintf(stderr, format, args);  /* Use vfprintf with va_list */
    fprintf(stderr, "\n");

    va_end(args);
}

/* Custom debug logging with line info */
void debug_log(const char *file, int line, const char *format, ...) {
    va_list args;
    va_start(args, format);

    printf("[DEBUG] %s:%d: ", file, line);
    vprintf(format, args);
    printf("\n");

    va_end(args);
}

#define DEBUG(...) debug_log(__FILE__, __LINE__, __VA_ARGS__)

/* Safe sprintf wrapper */
int safe_sprintf(char *buffer, size_t size, const char *format, ...) {
    va_list args;
    va_start(args, format);

    int result = vsnprintf(buffer, size, format, args);

    va_end(args);
    return result;
}

v-Functions Reference

printf-stylev-versionDescription
printfvprintfPrint to stdout
fprintfvfprintfPrint to file stream
sprintfvsprintfPrint to string
snprintfvsnprintfPrint to string (bounded)
scanfvscanfRead from stdin
fscanfvfscanfRead from file stream
sscanfvsscanfRead from string

Variadic Macros

C99 introduced variadic macros using __VA_ARGS__.

Basic Variadic Macro

#define DEBUG_PRINT(fmt, ...) \
    printf("[DEBUG] " fmt "\n", __VA_ARGS__)

/* Usage */
DEBUG_PRINT("Value: %d", 42);
DEBUG_PRINT("Name: %s, Age: %d", "John", 30);

Problem: Empty VA_ARGS

/* This doesn't work with zero variable arguments */
DEBUG_PRINT("Hello");  /* Error: trailing comma */

Solution 1: GNU Extension (##VA_ARGS)

#define DEBUG_PRINT(fmt, ...) \
    printf("[DEBUG] " fmt "\n", ##__VA_ARGS__)

/* The ## removes the trailing comma if __VA_ARGS__ is empty */
DEBUG_PRINT("Hello");  /* OK! */

Solution 2: C23 VA_OPT

/* C23 standard way */
#define DEBUG_PRINT(fmt, ...) \
    printf("[DEBUG] " fmt __VA_OPT__(,) __VA_ARGS__ "\n")

Practical Variadic Macro Examples

/* Logging with file and line */
#define LOG(level, fmt, ...) \
    fprintf(stderr, "[%s] %s:%d: " fmt "\n", \
            level, __FILE__, __LINE__, ##__VA_ARGS__)

#define LOG_INFO(fmt, ...)  LOG("INFO", fmt, ##__VA_ARGS__)
#define LOG_WARN(fmt, ...)  LOG("WARN", fmt, ##__VA_ARGS__)
#define LOG_ERROR(fmt, ...) LOG("ERROR", fmt, ##__VA_ARGS__)

/* Conditional debugging */
#ifdef DEBUG
    #define DBG(fmt, ...) printf("[DBG] " fmt "\n", ##__VA_ARGS__)
#else
    #define DBG(fmt, ...) ((void)0)
#endif

/* Assert with message */
#define ASSERT_MSG(cond, fmt, ...) \
    do { \
        if (!(cond)) { \
            fprintf(stderr, "Assertion failed: %s\n", #cond); \
            fprintf(stderr, fmt "\n", ##__VA_ARGS__); \
            abort(); \
        } \
    } while(0)

Best Practices

1. Always Call va_end

void func(int count, ...) {
    va_list args;
    va_start(args, count);

    /* Process... */

    va_end(args);  /* ALWAYS call this! */
}

2. Use the Correct Types

/* Remember type promotion! */
int get_char(int dummy, ...) {
    va_list args;
    va_start(args, dummy);

    /* WRONG: char is promoted to int */
    /* char c = va_arg(args, char); */

    /* CORRECT */
    char c = (char)va_arg(args, int);

    va_end(args);
    return c;
}

3. Validate Argument Count

int safe_sum(int count, ...) {
    if (count <= 0) {
        return 0;
    }

    va_list args;
    va_start(args, count);

    int sum = 0;
    for (int i = 0; i < count; i++) {
        sum += va_arg(args, int);
    }

    va_end(args);
    return sum;
}

4. Use GCC Attributes for Type Checking

/* Enable printf-style type checking */
__attribute__((format(printf, 2, 3)))
void log_message(int level, const char *fmt, ...);

/* Now compiler warns about mismatched types */
log_message(1, "Count: %d", "string");  /* Warning! */

5. Document Expected Arguments

/**
 * Calculate the sum of integers.
 *
 * @param count The number of integer arguments that follow
 * @param ...   Variable number of integers to sum
 * @return      The sum of all provided integers
 *
 * @warning All variable arguments must be of type int
 *
 * Example:
 *   int result = sum(3, 10, 20, 30);  // returns 60
 */
int sum(int count, ...);

Common Pitfalls

Pitfall 1: Wrong Type in va_arg

void wrong_usage(int count, ...) {
    va_list args;
    va_start(args, count);

    /* If caller passes int, but we read double: UNDEFINED! */
    double val = va_arg(args, double);  /* DANGER */

    va_end(args);
}

wrong_usage(1, 42);  /* Passes int, reads as double - WRONG! */

Pitfall 2: Reading Too Many Arguments

void dangerous(int count, ...) {
    va_list args;
    va_start(args, count);

    for (int i = 0; i < count + 5; i++) {  /* Reading past end! */
        printf("%d ", va_arg(args, int));   /* UNDEFINED */
    }

    va_end(args);
}

Pitfall 3: Forgetting Type Promotion

void process_chars(int count, ...) {
    va_list args;
    va_start(args, count);

    /* WRONG - char is promoted to int */
    for (int i = 0; i < count; i++) {
        char c = va_arg(args, char);  /* WRONG! */
        printf("%c", c);
    }

    va_end(args);
}

/* CORRECT version */
void process_chars_correct(int count, ...) {
    va_list args;
    va_start(args, count);

    for (int i = 0; i < count; i++) {
        char c = (char)va_arg(args, int);  /* CORRECT */
        printf("%c", c);
    }

    va_end(args);
}

Pitfall 4: Not Calling va_end

void leaky_function(int count, ...) {
    va_list args;
    va_start(args, count);

    if (count <= 0) {
        return;  /* WRONG: va_end not called! */
    }

    /* Process... */

    va_end(args);
}

/* CORRECT version */
void fixed_function(int count, ...) {
    va_list args;
    va_start(args, count);

    if (count <= 0) {
        va_end(args);  /* Clean up before return */
        return;
    }

    /* Process... */

    va_end(args);
}

Pitfall 5: Reusing va_list Without Reset

void wrong_reuse(int count, ...) {
    va_list args;
    va_start(args, count);

    /* First pass */
    for (int i = 0; i < count; i++) {
        printf("%d ", va_arg(args, int));
    }

    /* Second pass - WRONG! args is exhausted */
    for (int i = 0; i < count; i++) {
        printf("%d ", va_arg(args, int));  /* UNDEFINED! */
    }

    va_end(args);
}

/* CORRECT: Use va_copy */
void correct_reuse(int count, ...) {
    va_list args, args_copy;
    va_start(args, count);
    va_copy(args_copy, args);

    /* First pass */
    for (int i = 0; i < count; i++) {
        printf("%d ", va_arg(args, int));
    }

    /* Second pass - using the copy */
    for (int i = 0; i < count; i++) {
        printf("%d ", va_arg(args_copy, int));
    }

    va_end(args);
    va_end(args_copy);
}

Real-World Examples

1. Logging System

#include <stdio.h>
#include <stdarg.h>
#include <time.h>

typedef enum {
    LOG_DEBUG,
    LOG_INFO,
    LOG_WARNING,
    LOG_ERROR
} LogLevel;

static LogLevel min_level = LOG_DEBUG;
static FILE *log_file = NULL;

void log_init(LogLevel level, const char *filename) {
    min_level = level;
    if (filename) {
        log_file = fopen(filename, "a");
    }
}

void log_cleanup(void) {
    if (log_file) {
        fclose(log_file);
        log_file = NULL;
    }
}

void log_message(LogLevel level, const char *fmt, ...) {
    if (level < min_level) return;

    FILE *out = log_file ? log_file : stderr;

    /* Get timestamp */
    time_t now = time(NULL);
    struct tm *tm_info = localtime(&now);
    char time_buf[20];
    strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);

    /* Level names */
    const char *level_names[] = {"DEBUG", "INFO", "WARN", "ERROR"};

    /* Print header */
    fprintf(out, "[%s] [%s] ", time_buf, level_names[level]);

    /* Print message */
    va_list args;
    va_start(args, fmt);
    vfprintf(out, fmt, args);
    va_end(args);

    fprintf(out, "\n");
    fflush(out);
}

/* Convenience macros */
#define LOG_DBG(...)  log_message(LOG_DEBUG, __VA_ARGS__)
#define LOG_INF(...)  log_message(LOG_INFO, __VA_ARGS__)
#define LOG_WRN(...)  log_message(LOG_WARNING, __VA_ARGS__)
#define LOG_ERR(...)  log_message(LOG_ERROR, __VA_ARGS__)

2. Error Handler with Context

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int code;
    char message[256];
    char file[64];
    int line;
} Error;

Error *error_create(int code, const char *file, int line,
                    const char *fmt, ...) {
    Error *err = malloc(sizeof(Error));
    if (!err) return NULL;

    err->code = code;
    strncpy(err->file, file, sizeof(err->file) - 1);
    err->line = line;

    va_list args;
    va_start(args, fmt);
    vsnprintf(err->message, sizeof(err->message), fmt, args);
    va_end(args);

    return err;
}

void error_print(const Error *err) {
    if (!err) return;
    fprintf(stderr, "Error %d at %s:%d: %s\n",
            err->code, err->file, err->line, err->message);
}

#define ERROR(code, fmt, ...) \
    error_create(code, __FILE__, __LINE__, fmt, ##__VA_ARGS__)

3. Dynamic SQL Query Builder

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

char *build_insert_query(const char *table, int col_count, ...) {
    va_list args;
    char buffer[1024];
    char values[512];
    int offset = 0;
    int val_offset = 0;

    offset = snprintf(buffer, sizeof(buffer), "INSERT INTO %s (", table);
    val_offset = snprintf(values, sizeof(values), ") VALUES (");

    va_start(args, col_count);

    for (int i = 0; i < col_count; i++) {
        const char *col = va_arg(args, const char*);
        const char *val = va_arg(args, const char*);

        if (i > 0) {
            offset += snprintf(buffer + offset, sizeof(buffer) - offset, ", ");
            val_offset += snprintf(values + val_offset, sizeof(values) - val_offset, ", ");
        }

        offset += snprintf(buffer + offset, sizeof(buffer) - offset, "%s", col);
        val_offset += snprintf(values + val_offset, sizeof(values) - val_offset, "'%s'", val);
    }

    va_end(args);

    snprintf(values + val_offset, sizeof(values) - val_offset, ")");
    strncat(buffer, values, sizeof(buffer) - strlen(buffer) - 1);

    return strdup(buffer);
}

/* Usage */
/*
char *query = build_insert_query("users", 3,
    "name", "John",
    "email", "john@example.com",
    "age", "30");
// Result: INSERT INTO users (name, email, age) VALUES ('John', 'john@example.com', '30')
free(query);
*/

4. Menu Builder

#include <stdio.h>
#include <stdarg.h>

typedef void (*MenuAction)(void);

typedef struct {
    const char *label;
    MenuAction action;
} MenuItem;

void run_menu(const char *title, int item_count, ...) {
    va_list args;
    MenuItem items[20];

    va_start(args, item_count);
    for (int i = 0; i < item_count && i < 20; i++) {
        items[i].label = va_arg(args, const char*);
        items[i].action = va_arg(args, MenuAction);
    }
    va_end(args);

    while (1) {
        printf("\n=== %s ===\n", title);
        for (int i = 0; i < item_count; i++) {
            printf("%d. %s\n", i + 1, items[i].label);
        }
        printf("0. Exit\n");
        printf("Choice: ");

        int choice;
        if (scanf("%d", &choice) != 1) {
            while (getchar() != '\n');
            continue;
        }

        if (choice == 0) break;
        if (choice > 0 && choice <= item_count) {
            items[choice - 1].action();
        }
    }
}

Summary

Key Points

  1. •

    Header: Include <stdarg.h> for variadic function support

  2. •

    Macros:

    • •va_list - Type to hold argument information
    • •va_start(args, last_fixed) - Initialize the list
    • •va_arg(args, type) - Get next argument
    • •va_end(args) - Clean up (always call!)
    • •va_copy(dest, src) - Copy for reuse (C99)
  3. •

    Type Promotion:

    • •char and short → int
    • •float → double
  4. •

    Counting Methods:

    • •Explicit count parameter
    • •Sentinel value (NULL, 0, -1)
    • •Format string parsing
  5. •

    Best Practices:

    • •Always call va_end()
    • •Use correct types (account for promotion)
    • •Validate argument count
    • •Document expected argument types
    • •Use compiler attributes for type checking

Quick Reference

#include <stdarg.h>

int variadic_func(int count, ...) {
    va_list args;
    va_start(args, count);

    int result = 0;
    for (int i = 0; i < count; i++) {
        result += va_arg(args, int);
    }

    va_end(args);
    return result;
}

Common v-Functions

/* For printf-like wrapper functions */
vprintf(format, args);
vfprintf(file, format, args);
vsnprintf(buffer, size, format, args);

References

  1. •ISO/IEC 9899 - C Standard
  2. •GNU C Library Manual - Variadic Functions
  3. •C: A Reference Manual by Harbison and Steele
  4. •Expert C Programming by Peter van der Linden
  5. •The C Programming Language by Kernighan and Ritchie
README - C Programming Tutorial | DeepML