Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A variadic parameter is a language feature in C that allows a function to accept an indefinite number of arguments of varying types. It is denoted by an ellipsis (...) in the function signature and relies on a set of macros defined in the <stdarg.h> standard library header to traverse the argument list at runtime.

Syntax and Mechanical Implementation

Because C does not inherently track the number or types of arguments passed to a variadic function, specific macros are used to manage the argument state. Historically (C89 through C17), a variadic function required at least one explicitly named parameter before the ellipsis to act as an anchor for the compiler. As of the C23 standard, a function can be declared solely with an ellipsis. The following complete program demonstrates the strict sequence of macro invocations required to safely extract arguments, including both universally compatible and C23-specific syntax.
#include <stdio.h>
#include <stdarg.h>

// Pre-C23 (and universally valid): Requires at least one named parameter
void process_integers(int arg_count, ...) {
    // 1. va_list: An opaque data type holding the state and location of the arguments.
    va_list args;
    
    // 2. va_start: Initializes the va_list instance. 
    // Universally compatible syntax requires the last explicitly declared parameter.
    va_start(args, arg_count);
    
    printf("Processing %d integers:\n", arg_count);
    for (int i = 0; i < arg_count; i++) {
        // 3. va_arg: Retrieves the next argument and advances the internal state.
        // It relies on architecture-specific ABI alignment rules.
        int current_val = va_arg(args, int);
        printf("Argument %d: %d\n", i, current_val);
    }
    
    // 4. va_end: Invalidates the va_list object to prevent reuse.
    va_end(args);
}

// C23 and later: No named parameter required
#if __STDC_VERSION__ > 201710L
void process_c23(...) {
    va_list args;
    
    // C23 allows single-argument va_start
    va_start(args); 
    
    int val1 = va_arg(args, int);
    int val2 = va_arg(args, int);
    printf("C23 Processing: %d, %d\n", val1, val2);
    
    va_end(args);
}
#endif

int main(void) {
    process_integers(3, 10, 20, 30);
    
#if __STDC_VERSION__ > 201710L
    process_c23(40, 50);
#endif
    
    return 0;
}

Default Argument Promotions

A critical mechanical detail of C variadic parameters is default argument promotion. When arguments are passed through the ellipsis, the compiler automatically applies type promotions before passing them:
  1. Integer types smaller than int (e.g., char, short, _Bool) are promoted to int (or unsigned int).
  2. Floating-point types smaller than double (e.g., float) are promoted to double.
Consequently, passing a promoted type to va_arg is mandatory. Attempting to extract a char or float directly results in undefined behavior. The following program demonstrates the correct extraction of promoted types.
#include <stdio.h>
#include <stdarg.h>

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

    // CORRECT: Extracting promoted types
    // The caller passed a char, but it was promoted to int.
    char c = (char) va_arg(args, int);
    
    // The caller passed a float, but it was promoted to double.
    float f = (float) va_arg(args, double);

    printf("Extracted char: %c\n", c);
    printf("Extracted float: %.2f\n", f);

    /* 
     * INCORRECT usage (results in undefined behavior):
     * char bad_c = va_arg(args, char);   
     * float bad_f = va_arg(args, float); 
     */

    va_end(args);
}

int main(void) {
    char my_char = 'A';
    float my_float = 3.14f;

    // The compiler automatically promotes my_char to int and my_float to double
    process_promoted_types(2, my_char, my_float);

    return 0;
}

Memory and ABI Considerations

  • No Type Safety: va_arg performs no runtime type checking. If the type specified in va_arg does not match the actual type passed (post-promotion), the macro will misinterpret the memory or register contents, leading to corrupted data or undefined behavior.
  • No Boundary Checking: The <stdarg.h> macros cannot detect the end of the argument list. The caller and callee must establish a strict contract (such as a sentinel value, a format string, or an explicit count parameter) to prevent va_arg from reading out of bounds.
  • Pass-by-Value: Variadic arguments are always passed by value. To modify a variable from the caller’s scope, the caller must pass a pointer, and va_arg must extract it as a pointer type (e.g., va_arg(args, int*)).
  • State Duplication: If the argument list must be traversed multiple times, va_copy(va_list dest, va_list src) must be used to safely duplicate the state of an existing va_list. Both the original and the copy must independently be cleaned up with va_end.
Master C with Deep Grasping Methodology!Learn More