DMarlon Studio

Lesson 16 · 16 of 20

Variadic functions · C

Write C functions that take a variable number of arguments with stdarg.h: an average of n values and a printf-style logger.

C · Variadic functions in DMarlon Studio: the example's code and its output in the terminal

A variadic function accepts a variable number of arguments, like printf. It is declared with ... after at least one named parameter: double average(int count, ...). The tools live in `stdarg.h`.

Inside, you declare a va_list and start it with `va_start(args, count)`, which takes the last named parameter. Each `va_arg(args, double)` returns the next argument as the type you name, and `va_end` closes the list. Every va_start needs its va_end.

The function has no way to know how many arguments arrived or of what type: the caller has to tell it. average learns it from count; printf, from the % in its format. If they do not match, as in average(3, 7.5), it would read garbage without any warning. Also, through ..., floats arrive as double and chars and shorts as int, which is why va_arg asks for double.

log_msg is a printf-style logger: it prints the level in brackets and hands the format and the remaining arguments to `vprintf`, the version of printf that takes a va_list. There are also vfprintf, vsnprintf and friends, made precisely for this.

%% in a format prints a % sign, as in 91%.

Code · main.c

#include <stdarg.h>#include <stdio.h>// The first argument says how many numbers follow.double average(int count, ...) {    va_list args;    va_start(args, count);         // start after the last named parameter    double sum = 0;    for (int i = 0; i < count; i++)        sum += va_arg(args, double);  // floats arrive as double    va_end(args);                  // always pair va_start with va_end    return count ? sum / count : 0;}// A printf-like logger: the format string says what the extra arguments are.void log_msg(const char *level, const char *format, ...) {    va_list args;    va_start(args, format);    printf("[%-5s] ", level);    vprintf(format, args);         // the v... versions take a va_list    printf("\n");    va_end(args);}int main(void) {    printf("Average of 3: %.2f\n", average(3, 7.5, 9.0, 6.0));    printf("Average of 5: %.2f\n", average(5, 4.0, 8.0, 15.0, 16.0, 23.0));    printf("Average of 0: %.2f\n", average(0));    log_msg("INFO", "Server started on port %d", 8080);    log_msg("WARN", "Disk %s is %d%% full", "/data", 91);    log_msg("ERROR", "Order %s: %d items, total %.2f", "A-104", 3, 59.85);    return 0;}

Output · cc main.c -o main && ./main

Average of 3: 7.50
Average of 5: 13.20
Average of 0: 0.00
[INFO ] Server started on port 8080
[WARN ] Disk /data is 91% full
[ERROR] Order A-104: 3 items, total 59.85

Key points

  • ... comes after a named parameter.
  • va_start, va_arg and va_end, always starting and ending in pairs.
  • The caller states how many arguments there are and their types.
  • floats arrive as double; chars as int.

Exercise

Write int max_of(int count, ...) that returns the largest of count integers, and a version of log_msg that writes into a buffer with vsnprintf instead of the screen.