C/C++ Arena

Step 6 of 7

Variadic functions

printf takes one argument, or five, or twenty. Functions like that are variadic, and you can write your own with <stdarg.h>. The ... in the parameter list stands for "any number of extra arguments":

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

/* Average of the `count` ints that follow. */
double average(int count, ...) {
    va_list args;
    va_start(args, count);                    /* extras start after `count` */
    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);           /* the next extra, read as an int */
    }
    va_end(args);
    return count > 0 ? (double)total / count : 0.0;
}

/* A printf-style logger: hand the format and the extras to vprintf. */
void log_line(int line, const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    printf("[line %d] ", line);
    vprintf(fmt, args);
    printf("\n");
    va_end(args);
}

#define LOG(...) log_line(__LINE__, __VA_ARGS__)

int main(void) {
    printf("%.2f\n", average(3, 90, 72, 85));
    LOG("loaded %d maps in %.2f s", 7, 0.25);
    return 0;
}
82.33
[line 30] loaded 7 maps in 0.25 s

How it works

Why printf format mistakes are dangerous

This explains an earlier rule. printf finds out what each extra argument is only from the format string. Pass a double where the format says %d and printf reads the wrong bytes: undefined behavior. For printf itself, compilers check the format string against the arguments and warn, which is one more reason to read warnings. Your own variadic functions get no such check.

Your task

Write max_of(count, ...), returning the largest of count ints (count is at least 1), and format_into(buf, size, fmt, ...), which works exactly like snprintf: it formats into buf, never writing more than size bytes, and returns what snprintf would.

Challenge 2

Write int sum_all(int count, ...), which adds up count ints, and double average_of(int count, ...), which averages count doubles (count is at least 1). Each reads its extra arguments with va_arg using the matching type.

Challenge 3

Write int log_msg(char *buf, size_t size, const char *level, const char *fmt, ...) that writes [LEVEL] followed by the formatted message, like [WARN] 3 players left. It must never write more than size bytes, and it returns the total length it wanted to write, like snprintf.

Previous: Command-line arguments Next: Challenge: a real command-line tool