Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

C Language Dictionary

  1. Home
  2. C Language Dictionary
  3. va_list / va_start() / va_arg() / va_end()

va_list / va_start() / va_arg() / va_end()

Since: C89(1989)

These are macros and a type used to implement "variadic functions" — functions that accept a variable number and type of arguments from the caller. They are defined in <stdarg.h>, and standard library functions such as printf() use this mechanism internally.

Syntax

va_list ap;

// Initializes variadic argument processing. last_named is the name of the last fixed parameter.
void va_start(va_list ap, last_named);

// Retrieves the next variadic argument as the specified type.
// Returns: the value of the next argument (type is specified by type).
type va_arg(va_list ap, type);

// Ends variadic argument processing (must always be called).
void va_end(va_list ap);

// Copies the current state to another va_list (C99 and later).
void va_copy(va_list dest, va_list src);

Macros and Type Reference

NameKindDescription
va_listTypeA type that holds the state of a variadic argument list (such as the current position).
va_start()MacroStarts reading variadic arguments. Must be called first.
va_arg()MacroRetrieves the next argument. You must specify the correct type for the argument.
va_end()MacroEnds variadic argument processing. Must be called after every va_start().
va_copy()MacroCopies the current position of one va_list to another (C99 and later).

Sample Code

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

/* Accepts count integers and returns their sum. */
int sum(int count, ...) {
    va_list ap;
    va_start(ap, count); /* count is the last fixed parameter. */

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

    va_end(ap); /* Always call va_end. */
    return total;
}

/* A printf wrapper that prepends a [DEBUG] prefix. */
void debug_printf(const char *format, ...) {
    printf("[DEBUG] ");
    va_list ap;
    va_start(ap, format);
    vprintf(format, ap); /* vprintf accepts a va_list directly. */
    va_end(ap);
}

int main(void) {
    printf("sum(3, 1,2,3) = %d\n", sum(3, 1, 2, 3));
    printf("sum(5, 10,20,30,40,50) = %d\n", sum(5, 10, 20, 30, 40, 50));

    debug_printf("Value is %d\n", 42);
    debug_printf("Name: %s\n", "Son Goku");

    return 0;
}

Run the following command:

gcc va_list_va_start_va_arg.c -o va_list_va_start_va_arg
./va_list_va_start_va_arg
sum(3, 1,2,3) = 6
sum(5, 10,20,30,40,50) = 150
[DEBUG] Value is 42
[DEBUG] Name: Son Goku

Using a Sentinel to Signal the End of Arguments

Instead of a count parameter, you can use a special terminator (sentinel) value to mark the end of the argument list.

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

/* Uses -1 as a sentinel to terminate the integer list. */
int sum_until_sentinel(int first, ...) {
    va_list ap;
    va_start(ap, first);

    int total = 0;
    int v = first;
    while (v != -1) {
        total += v;
        v = va_arg(ap, int);
    }

    va_end(ap);
    return total;
}

/* Uses NULL as a sentinel to terminate the string list. */
void print_names(const char *first, ...) {
    va_list ap;
    va_start(ap, first);

    const char *name = first;
    while (name != NULL) {
        printf("- %s\n", name);
        name = va_arg(ap, const char *);
    }

    va_end(ap);
}

int main(void) {
    printf("Sum: %d\n", sum_until_sentinel(10, 20, 30, -1));
    printf("Sum: %d\n", sum_until_sentinel(5, 15, -1));

    printf("Members:\n");
    print_names("Son Goku", "Vegeta", "Piccolo", NULL);

    return 0;
}

Run the following command:

gcc va_sentinel.c -o va_sentinel
./va_sentinel
Sum: 60
Sum: 20
Members:
- Son Goku
- Vegeta
- Piccolo

Common Mistakes

Common Mistake: Passing the Wrong Type to va_arg

If the type passed to va_arg() does not match the actual argument type, the result is undefined behavior. The int / double mix is a particularly common mistake.

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

/* NG: passing doubles but reading as int */
void wrong_type(int count, ...) {
    va_list ap;
    va_start(ap, count);
    for (int i = 0; i < count; i++) {
        int v = va_arg(ap, int); /* reading double as int → undefined behavior */
        printf("%d\n", v);
    }
    va_end(ap);
}

/* OK: types match on both sides */
void correct_type(int count, ...) {
    va_list ap;
    va_start(ap, count);
    for (int i = 0; i < count; i++) {
        double v = va_arg(ap, double);
        printf("%.2f\n", v);
    }
    va_end(ap);
}

int main(void) {
    /* NG: doubles passed but read as int */
    /* wrong_type(2, 1.5, 2.5); */ /* undefined behavior */

    /* OK: doubles passed and read as double */
    correct_type(2, 1.5, 2.5);

    return 0;
}

Run the following command:

gcc va_type_ng.c -o va_type_ng
./va_type_ng
1.50
2.50

Notes

To define a variadic function, add ... (an ellipsis) at the end of the parameter list. The function must have at least one fixed parameter (for example, the format string is the first parameter of printf).

Passing the wrong type to va_arg() results in undefined behavior. Specifying double for an integer argument, or attempting to read beyond the actual number of arguments, can cause crashes or unexpected values. The caller and the implementation must agree on the number and types of arguments through their own mechanism — such as a count parameter, a sentinel value, or a format string.

If you need to pass a variadic argument list to another function that accepts a va_list (such as vprintf or vsprintf), either pass the va_list directly or use va_copy() to duplicate it. Calling va_end() twice on the same va_list results in undefined behavior.

Since C99, you can also use variadic macros (__VA_ARGS__) to achieve similar functionality at the macro level. This is commonly used for debug logging macros.

If you find any errors or copyright issues, please .