Bobbing for Kernels

See Bob. See Bob bob. Bob, Bob, bob!

Variadic functions in C: a new idiom?

Posted by kernelbob on April 5, 2010

For a long time, C has let you write functions that take a variable number of arguments.  But C leaves it up to each variadic function to figure out how many arguments it’s been passed, and up to each caller to pass the right arguments.

Here’s a cute hack to make variadic functions easier to write and a little harder to call incorrectly.

As a contrived example, let’s write a function that copies a tag and a sequence of floating-point arguments into a linked list allocated on the heap.  The first attempt might look like this.

#include <stdarg.h>

typedef struct node node;
    struct node {
    double datum;
    char   tag;
    node  *next;
};

node *link_list_A(char tag, int count, ...)
{
    node *head = NULL;
    node **patch = &head;
    int i;
    va_list ap;

    va_start(ap, count);
    for (i = 0; i < count; i++) {
        node *p  = malloc(sizeof *p);
        p->datum = va_arg(ap, double);
        p->tag   = tag;
        p->next  = NULL;
        *patch   = p;
        patch    = &p->next;
    }
    va_end(ap);
    return head;
}

You’d call it like this.

    node *p = link_list_A('A', 5, 1.0, 1.5, 2.0, 2.5, 3.0);

There’s just a little problem with that.  What happens if you go back later and change the arguments?  You have to remember to change the count.  Also, you might miscount and tell it it has 16 arguments when it really has 17.

Another approach is to use an end marker instead of a count.

# define ENDARG (-1.0)

node *link_list_B(char tag, ...)
{
    node *head = NULL, **patch = &head;
    double datum;
    va_list ap;

    va_start(ap, tag);
    while ((datum = va_arg(ap, double)) != ENDARG) {
        node *p  = malloc(sizeof *p);
        p->tag   = tag;
        p->datum = datum;
        p->next  = NULL;
        *patch   = p;
        patch    = &p->next;
    }
    va_end(ap);
    return head;
}

    node *p = link_list_B('B', 1.0, 1.5, 2.0, 2.5, 3.0, ENDARG);

That’s better.  But wouldn’t it be even nicer if we could eliminate ENDARG in the caller?  It’s a big wart on the readability of the program.

C99 has variadic macros as well as variadic functions.  By wrapping the function in a macro, the macro can supply the end marker instead of sprinkling it throughout the source code.

(Hey, C99 has it’s been ratified for eleven years.  Don’t look so scandalized.)

#define link_list_C(tag, ...) \
    (link_list_B(tag, __VA_ARGS__, ENDARG)) // link_list_B defined above

Now you call it like this.

    node *p = link_list_C('C', 1.0, 1.5, 2.0, 2.5, 3.0);

It’s not a big deal, but it makes the code a little more readable. And it’s an idiom I haven’t seen before.

I use the idiom several places in my Scheme interpreter, schetoo.  For example, Schetoo has a C macro, MAKE_LIST, which is analogous to Scheme’s list procedure.

Scheme:

(list 'a 'b (list 'c))
=> (a b (c))

C:

obj_t a = make_symbol_from_C_str(L"a");
obj_t b = make_symbol_from_C_str(L"b");
obj_t c = make_symbol_from_C_str(L"c");
MAKE_LIST(a, b, MAKE_LIST(c));
=> (a b (c))

It’s a pretty good analogue, I think.

The source for schetoo is on github.

About these ads

5 Responses to “Variadic functions in C: a new idiom?”

  1. Hi,
    I think you could even have a macro and a function of the same name, if you like:

    #define link_list_B(tag, ...) \
        (link_list_B(tag, __VA_ARGS__, ENDARG)) // function link_list_B
    

    The link_list_B would not get expanded, since the preprocessor inhibits recursion automatically. You only have to ensure that the macro definition comes after the function.

    Jens

  2. Mac said

    You don’t need ENDARG in all cases, because there’s a way to count the number of arguments given to a variadic macro:
    http://groups.google.com/group/comp.std.c/browse_thread/thread/77ee8c8f92e4a3fb/346fc464319b1ee5

  3. But what if one of the numbers you mean to enter in is a (-1.0) then it stops when it gets to it, is there something else that could be used for ENDARG? I tried NULL but it errors on that
    Invalid operands to binary != (have ‘double’ and ‘void *’)
    Garrett

    • kernelbob said

      Sure. You can use a different value. If you’re using floats, maybe FLOAT_MAX, defined in limits.h. I _said_ the example was contrived…

      • Ya I just used
        #define ENDARG (4.94065645841e-324)
        which I found to be the smallest double possible. And I use NULL if I’m pulling in strings. This article helped me a lot and forced me to start thinking about macros. So as longs as I’m not pulling (4.94065645841e-324) into a function I should be ok; and if I’m doing that I have bigger problems. Thank you a lot.

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: