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
| Name | Kind | Description |
|---|---|---|
| va_list | Type | A type that holds the state of a variadic argument list (such as the current position). |
| va_start() | Macro | Starts reading variadic arguments. Must be called first. |
| va_arg() | Macro | Retrieves the next argument. You must specify the correct type for the argument. |
| va_end() | Macro | Ends variadic argument processing. Must be called after every va_start(). |
| va_copy() | Macro | Copies 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 contact us.