C Standard Library stdarg.h

Beginner
⏱️ 12 min read
📚 Updated: Jul 2026
🎯 5 Examples
<stdarg.h>

What You’ll Learn

<stdarg.h> lets you write functions that accept a variable number of arguments, like printf. The caller passes extra values after a fixed parameter; inside the function you use va_start, va_arg, and va_end to walk through them.

01

va_list

Arg walker.

02

va_start

Begin list.

03

va_arg

Next value.

04

va_end

Cleanup.

05

...

Ellipsis.

06

count

Know arity.

Definition and Usage

A variadic function ends its parameter list with ... (ellipsis). At least one named parameter must come before ...—often a count or format string that tells the function how many or what types follow.

The compiler does not type-check arguments after .... You must call va_arg with the correct type each time. Wrong types cause undefined behavior.

💡
Beginner Tip

Always call va_end before returning from the function (every path). The old reference labeled va_list as a macro—it is a type; va_start / va_arg / va_end are the macros.

📝 Syntax

Include the header:

C
#include <stdarg.h>

Variadic function declaration

C
int sum(int count, ...);   /* count is last fixed arg before ... */

Macro and type reference

  • va_list — type for the argument pointer (e.g. va_list ap;).
  • va_start(ap, last) — initialize ap; last is the last named parameter.
  • va_arg(ap, type) — fetch next argument as type.
  • va_end(ap) — end traversal; required cleanup.
  • va_copy(dest, src) — copy state (C99); call va_end on both when done.

Default argument promotion

  • char and short → use va_arg(ap, int).
  • float → use va_arg(ap, double).
  • float literals in variadic calls are promoted to double.

Headers and linking

  • #include <stdarg.h> — no special link flag.
  • Compile: gcc program.c -std=c11 -o program

⚡ Quick Reference

StepMacroPurpose
1va_list ap;Declare walker
2va_start(ap, last)Point at first ... arg
3va_arg(ap, int)Read next arg
4va_end(ap)Cleanup
Aritycount or NULLKnow when to stop
Start
va_start(ap, count)

After fixed args

Read
va_arg(ap, int)

Match real type

End
va_end(ap)

Always call

float
va_arg(ap, double)

Promotion rule

Examples Gallery

Compile with gcc file.c -std=c11 -o out. Every variadic function needs a rule for how many arguments to read and of what type.

📚 Getting Started

Count-based variadic sum from the reference.

Example 1 — Sum Variable Integers with a Count

Classic pattern: first argument is how many int values follow.

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

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

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

    return total;
}

int main(void) {
    printf("Sum of 3, 5, 7: %d\n", sum(3, 3, 5, 7));
    printf("Sum of 1..5: %d\n", sum(5, 1, 2, 3, 4, 5));

    return 0;
}

How It Works

va_start(args, count) uses count as the anchor parameter. The loop calls va_arg(args, int) exactly count times—no more, no less.

Example 2 — Average of doubles

Same count pattern with double (remember float args promote to double).

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

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

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

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

int main(void) {
    printf("Avg: %.2f\n", average(4, 10.0, 20.0, 30.0, 40.0));
    return 0;
}

How It Works

Passing 10.0 etc. as double matches va_arg(args, double). If callers passed bare float, you would still read with double due to promotion.

📈 Practical Patterns

Sentinel values, logging, and max-finding.

Example 3 — Strings Until NULL Sentinel

No count parameter—stop when you see a NULL pointer.

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

void print_words(const char *first, ...) {
    va_list args;
    const char *word = first;

    printf("Words:");
    while (word != NULL) {
        printf(" %s", word);
        if (word == first) {
            va_start(args, first);
            word = va_arg(args, const char *);
        } else {
            word = va_arg(args, const char *);
        }
    }
    va_end(args);
    printf("\n");
}

int main(void) {
    print_words("hello", "variadic", "world", (const char *)NULL);
    return 0;
}

How It Works

The first word is a normal parameter; the rest live in .... Loop until NULL. Always document the sentinel convention—callers must terminate the list.

Example 4 — Tiny debug_log Wrapper

Printf-style idea: level string plus a message (fixed types for simplicity).

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

void debug_log(const char *level, const char *fmt, ...) {
    va_list args;

    printf("[%s] ", level);
    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);
    printf("\n");
}

int main(void) {
    debug_log("INFO", "User %s logged in (id=%d)", "Alice", 42);
    debug_log("WARN", "Retries left: %d", 3);

    return 0;
}

How It Works

Real printf parses fmt to know types. Here we delegate to vprintf from <stdio.h>—the standard helper that accepts a va_list.

Example 5 — Maximum of N Integers

Requires at least one value; returns the largest.

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

int max_int(int count, ...) {
    va_list args;
    int i, m;

    if (count <= 0) {
        return 0;
    }

    va_start(args, count);
    m = va_arg(args, int);
    for (i = 1; i < count; i++) {
        int v = va_arg(args, int);
        if (v > m) {
            m = v;
        }
    }
    va_end(args);

    return m;
}

int main(void) {
    printf("Max: %d\n", max_int(5, 3, 9, 1, 9, 4));
    return 0;
}

How It Works

Seed m with the first va_arg, then compare each subsequent value. Validate count > 0 before touching the list.

🚀 Common Use Cases

  • printf / scanf family — format-driven variadic I/O.
  • Logging librarieslog_info("...", ...) wrappers.
  • Error reportingwarn(const char *fmt, ...).
  • Math utilities — sum, min, max over many numbers.
  • String builders — concatenate unknown number of C strings.
  • Testing frameworks — assertion macros with optional messages.

🧠 How stdarg.h Works

1

Caller pushes extras

Arguments after the last fixed param go on stack per calling convention.

Call site
2

va_start locates them

Uses address of last fixed arg to find first ... value.

Initialize
3

va_arg reads each

Advances pointer by size of requested type.

Iterate
=

va_end finishes

Cleanup; function returns result built from the args.

📝 Notes

  • You cannot pass ... to another function directly—use va_list and helpers like vprintf.
  • Wrong va_arg type corrupts the walk and may crash.
  • Reading past the last real argument is undefined behavior.
  • C++ has safer alternatives (std::initializer_list, templates); C uses stdarg.
  • va_end must run on every path, including early return.
  • Variadic macros in C99 use __VA_ARGS__—different feature, often paired with logging.

⚡ Optimization

Variadic calls add slight overhead versus fixed parameters. For hot paths, pass an array and length instead of ten separate arguments. For logging, the cost is dominated by I/O, not va_arg itself.

Conclusion

<stdarg.h> powers flexible APIs like printf. Declare ..., anchor with a fixed parameter, then va_start / va_arg / va_end. Document how callers specify arity and types.

When type safety matters more than flexibility, use fixed arrays, structs, or counted buffers instead of variadic functions.

💡 Best Practices

✅ Do

  • Pass a count or format string to know arity/types
  • Call va_end on every exit path
  • Use double in va_arg for promoted floats
  • Document expected argument order and types
  • Use vprintf / vsnprintf for format strings

❌ Don’t

  • Guess types when calling va_arg
  • Forget va_end after va_start
  • Use va_arg(ap, char) for promoted small integers
  • Read more arguments than the caller passed
  • Overuse variadic functions when an array suffices

Key Takeaways

Knowledge Unlocked

Five things to remember about stdarg.h

Variable arguments in C, explained simply.

5
Core concepts
📚 02

va_arg

Next arg.

Read
📈 03

va_end

Cleanup.

Required
📄 04

count

Know N.

Arity
🌐 05

promotion

float→double.

Types

❓ Frequently Asked Questions

stdarg.h provides macros to read variable arguments in functions declared with ... (ellipsis). va_start initializes a va_list, va_arg fetches each argument by type, and va_end cleans up. printf and scanf use this machinery internally.
va_start(ap, last) needs the name of the last fixed parameter before the ... arguments. The implementation uses it to find where the variable args begin on the stack. It must be the parameter immediately before ...
printf reads the format string (%d, %f, %s) to decide which type to pass to va_arg each time. Your functions must use a similar convention—pass a count, a format string, or a sentinel—because the compiler does not type-check ... arguments.
In variable argument lists, char and short are promoted to int, and float is promoted to double. Always use va_arg(ap, int) for promoted char/short and va_arg(ap, double) for float values passed through ...
va_list is a type (typedef), not a macro. va_start, va_arg, va_end, and va_copy (C99) are macros. You declare va_list ap; then call va_start(ap, last_fixed_arg).
Use them for logging, printf-style APIs, or generic wrappers where arity varies. For most application code, fixed parameters, arrays, or structs are safer. Prefer stdarg when you are building a small library API that truly needs flexible arity.
Did you know?

The C standard library exposes vprintf, vfprintf, and vsnprintf so wrapper functions can forward a va_list without re-parsing .... That is how a one-line #define log(...) my_log(__VA_ARGS__) can still reach a shared variadic core.

Explore C Standard Library Headers

Continue with stdbool.h or browse the library index.

Standard Library Index →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

5 people found this page helpful