Coding

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);
    vprintf(format, args); // Or any function that takes a va_list parameter
    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.

LinuxJedi

View Comments

  • FWIW, I think line 17 in the last code sample should read:

    my_print_func2(format, args);

    Thank you for this, I learned something new!

    • There is definitely a bug there, thanks for pointing it out. Although it shouldn't call `my_print_func2`, that would fail due to a missing parameter and also get in a loop that would blow the stack. It should have been `vprintf()` or any function that takes a `va_list`. I'll fix it now.

  • Thanks for this artucle Jedi, it truly helped.

    I wish you could elaborate on why this justifies a macro.

    specifically I'm a bit puzzled regarding this quote of yours:

    "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."

    X_X

    Regardless, thank you for this post!

    • I don't remember the exact problem I was trying to solve, it was over 4 years ago. But I believe `my_print_func()` was part of a library I was using and couldn't alter it. The library had a way adding your own `printf()` function, and a callback which was `my_print_func2()`. I think I was trying to turn on/off debug/verbose printing in the library and this was the only way around it.

Recent Posts

Upgrading the RAM Detective: A Firmware Adventure with RAMCHECK

The firmware in my RAMCHECK is very old, there were many updates since then. Unfortunately,…

3 weeks ago

The Ultimate RAM Detective: Meet the Innoventions RAMCHECK

Whilst repairing vintage machines, a lot of RAM passes by my benches. Most of it…

1 month ago

Vintage Speed Demon: Fixing an ARK1000VL Graphics Card

According to some, the ARK1000VL is considered the fastest VLB graphics card chip you can…

2 months ago

Using rr On Newer Intel CPUs

If, like me, you have a newer Intel hybrid CPU, with P-Cores and E-Cores, you…

2 months ago

The Legend Continues: Amiga 1000 Keyboard Revival

I have restored the boxed Amiga 1000 main unit and the mice that came with…

2 months ago

Amiga 4000 Repair: This one was just weird

I was recently sent an Amiga 4000 motherboard repair. It should have been quite straightforward,…

2 months ago