Nested Variadic Functions in C

You may be familiar with variadic functions in C, these are basically functions that allow a variable number of parameters, they are normally written like this:

void my_print_func(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}

Obviously you can do more with them and they are very useful, but if you want to have one function calling another things can get complicated. This post explores the problem and a couple of ways of solving it.

The core problem is that a va_list can only be used once, if you try to do the following you are in for a bad time:

void my_print_func(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}

void my_print_func2(bool should_print, const char *format, ...)
{
    if (!should_print)
    {
        return;
    }
    va_list args;
    va_start(args, format);
    my_print_func(format, args);
    va_end(args);
}

This will likely compile, but your text will get quite scrambled along the way when it is executed. So, you have two options, the first is to pass the va_list to the parent function like this:

void my_print_func(const char *format, va_list args)
{
    vprintf(format, args);
}

void my_print_func2(bool should_print, const char *format, ...)
{
    if (!should_print)
    {
        return;
    }
    va_list args;
    va_start(args, format);
    my_print_func(format, args);
    va_end(args);
}

This is fine if you own my_print_func() and the call chain is only going to work like this. But if, like me, you are in a scenario where my_print_func() and my_print_func2() could be called, what can you do? Well, this is where macros come in.

Basically we define my_print_func2() as a macro which calls my_print_func(), the macro can have a small amount of logic if needed. Note that you need to be compiling with at least C99 standard for this to work:

#define my_print_func2(should_print, format, ...)\
    do {\
        if (!should_print)\
        {\
            return;\
        }\
        else\
        {\
            my_print_func(format, __VA_ARGS__);\
        }\
    } while (0)

void my_print_func(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    my_print_func(format, args);
    va_end(args);
}

Since the macro essentially fills in code at compile time to where it is used it is essentially just one call to my_print_func(), but it acts like a separate call to the reader.

Published by

LinuxJedi

Lead Software Engineer / Manager for the MariaDB Corporation and an Open Source Software advocate.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.