mikeash.com: just this guy, you know?

Posted at 2009-08-21 15:15 | RSS feed (Full text feed) | Blog Index
Next article: Reading Between the Lines of Apple's FCC Reply
Previous article: Friday Q&A 2009-08-14: Practical Blocks
Tags: c fridayqna macro vararg
Friday Q&A 2009-08-21: Writing Vararg Macros and Functions
by Mike Ash  

Welcome to another Friday Q&A, where all references are strong and all values are above average. This week I'm going to talk about how to write macros and functions that take variable arguments in C, as suggested by Damien Sorresso.

Macros
Writing a vararg macro is pretty simple in principle. Note that unlike functions, vararg macros are new as of C99, so they won't work with earlier dialects. You create one by putting ... at the end of the macro's argument list, like so:

    #define vararg_macro(a, b, c, ...)
And then you access them by using __VA_ARGS__, which just expands to the arguments provided, separated by commas just like they were provided.

Here's an example of a debug logging macro using this technique:

    #define DEBUG_LOG(...) do { \
        if(gDebugLoggingEnabled) { \
            fprintf(stderr, "Debug log:" __VA_ARGS__); \
            fprintf(stderr, "\n") \
        } \
    } while(0)
If you haven't seen it before, the do/while construct is a common way to construct a multi-statement macro which is actually a single statement. The worth of this can be seen in this hypothetical code:
    if(!condition)
        DEBUG_LOG("condition was false!");
    else
        do_something_important();
If this macro were written without the do/while wrapping, this code would fail in hilarious ways.

Now let's say we wanted to add logging of the file name and line number where the log is, using the __FILE__ and __LINE__ macros. We could do this by adding a third fprintf line, but imagine we want to combine it into the first line instead. This is easy enough to do:

    #define DEBUG_LOG(fmt, ...) do { \
        if(gDebugLoggingEnabled) \
            fprintf(stderr, "Debug log, %s:%d: " fmt "\n", __FILE__, __LINE__, __VA_ARGS__); \
    } while(0)
This works, but it has a problem: it requires at least one argument besides the format string. You can't just do DEBUG_LOG("condition was false!") anymore, because that leaves a dangling comma at the end.

The easiest solution to this is to take advantage of a gcc-specific extension. Putting ## in between the comma and the __VA_ARGS__ will remove the comma when __VA_ARGS__ is empty:

    #define DEBUG_LOG(fmt, ...) do { \
        if(gDebugLoggingEnabled) \
            fprintf(stderr, "Debug log, %s:%d: " fmt "\n", __FILE__, __LINE__, ## __VA_ARGS__); \
    } while(0)
Now everything works as expected! Of course this code is not strictly C99 compliant anymore, so beware.

Functions
Vararg functions aren't that much harder. The declaration is pretty much the same as for a macro: put ... at the end of the argument list. One important difference can be seen here: the ... must not be the only parameter the function takes. In other words, a vararg function must take at least one fixed parameter.

In the body of the function, you'll want to make sure to do #include to get the appropriate declarations, then you can use some simple functions to work with the argument list. The va_list type describes a variable argument list. Then you call va_start on it to initialize it. To do your actual work, call va_arg in a loop to pop arguments, and when you're all done you call va_end to terminate processing.

Note that these are the only three operations supported. It's not possible to query the argument list for length, for type, or anything else like that. You must take care of these things yourself by arranging a convention with the caller.

Let's write a quick example. Imagine that for some reason you find yourself frequently needing to post several NSNotifications at a time. To cut down on the work required, we can write a vararg function that takes a number of notifications as the parameters. This is what the declaration will look like:

    void PostNotifications(id obj, NSString *firstNotificationName, ... /* terminate with nil */)
Notice how I just document that the caller must terminate the list with nil. Since I can't query for the length of the list, that's how we'll know when to stop. Also notice how firstNotificationName is a fixed parameter. This will make the following code a little simpler.

Next, we'll set up the va_list:

    {
        va_list args;
        va_start(args, firstNotificationName);
To call va_start we have to tell it what the last fixed parameter is. This is why there needs to be at least one fixed parameter.

Next, we'll run a loop to post the notifications:

        NSString *notificationName = firstNotificationName;
        while(notificationName)
        {
            [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:obj];
            notificationName = va_arg(args, NSString *);
        }
Then clean up:
        va_end(args);
    }
Now we can use it like so:
    PostNotifications(self, FirstNotificationName, SecondNotificationName, ThirdNotificationName, nil);
Easy!

This is kind of fragile, because you'll crash if the caller forgets to terminate the list, and anything could happen if he passes in something of the wrong type (like a float) by accident, but that's just how vararg functions work in C.

Conclusion
That wraps up this week's Friday Q&A. Vararg macros and functions are a little strange but they can be handy, and once you know the basics they're not too hard to make at all.

Come back next week for another exciting edition. As always, Friday Q&A is driven by you, the reader. If you have any suggestions for a future topic of discussion, post it in the comments or e-mail it to me. Without your suggestions, Friday Q&A could not happen, so send them in!

Did you enjoy this article? I'm selling whole books full of them! Volumes II and III are now out! They're available as ePub, PDF, print, and on iBooks and Kindle. Click here for more information.

Comments:

Very convenient, but you left out the most confusing one: vararg methods!

Like vararg functions, vararg methods require at least one fixed parameter. The va_list/va_start/va_arg API is the same. And they are declared like this:

- (NSString *)stringByAppendingStrings:(NSString *)first, ...;

And the restriction of one fixed argument is a lot more arbitrary here, because, as we well know, an Objective-C method is passed two hidden arguments (self and _cmd). In theory I would expect va_start(_cmd) to work, but I wouldn't use it in practice, especially given potential compiler differences in this field!

Finally, there's a nice macro called NS_REQUIRES_NIL_TERMINATION that expands to a GCC __attribute__ ((sentinel)), which will warn when any invocations of the function aren't nil-terminated (if you have -Wformat turned on). This works for both functions and methods.
Another omission is the "gotcha" related to (IIRC) argument promotion and callers passing an integer 0 instead of a zero *pointer* to terminate the argument list.

This causes problems (again, IIRC) on 64-bit systems.
I actually discuss the 0 problem, in a slightly different context, in the Beware of NULL section of Format Strings Tips and Tricks from a month ago:

http://www.mikeash.com/?page=pyblog/friday-qa-2009-07-17-format-strings-tips-and-tricks.html
You say that using do/while(0) is a great way to put multiple statements in a macro (if you don't want it to fail in places where a single statement is expected), but the same can be achieved simply by using a pair of curly braces (which you use for while anyway since while expects a single statement/block) to create a new scope/block:

#define FOO { method1(); method2(); }

IMO this is idential to the while construct in every aspect (variable scope, ...).

A nice complement to this article: Variable argument lists in Cocoa
http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html

Worth a read.
No, curly braces do not achieve the same effect. Go look at my example if/else code and consider what happens if you use your style in there. It will not compile.
Ok, so you were referring to the semicolon which is required after the do/while but won't work after the { } block. Thanks for the explanation.
It might be worth mentioning that on a number of architectures variadic functions have substantially different calling conventions that non-variadic functions. In particular, on platforms that use registers to pass arguments they tend to shove all of the args into GPRs even though the standard calling convention might have you place singles and doubles in FPRs, since the compiler doesn't have any type info for the va_args.

This manifests itself in several ways. The first is slightly less efficient code (copying stuff back and forth from fprs to gprs, or extra copies to the stack), the second is that if you ignore missing prototype warnings (everyone compiles with -Wall=error, right) you may get code that links correctly but crashes because the calling function doesn't put things where the called function expects them.
In particular, on platforms that use registers to pass arguments they tend to shove all of the args into GPRs even though the standard calling convention might have you place singles and doubles in FPRs


That not quite true. See for example on PPC:

printf("%f", 10.0);

Generate this assembly:
=> fmr f1,f0
    bl _fprintf$LDBL128

And it uses mmx register on x86_64.

So you can see that even on arch that uses register to pass argument, floating point register are use to pass floating point values.

Regardless of the actual calling conventions, you should never write code that calls functions without prototypes. Aside from being non-conforming code even if it does happen to work on your particular platform, it's also all too easy to screw up your types. For example, it is impossible to correctly call a function which takes a float parameter in this way, because a float passed to a prototype-less function will always be promoted to double. It's also easy to accidentally pass 0 instead of 0.0, which would normally cause a harmless implicit conversion but in the absence of a prototype will screw you over hard.
Some more detail on multi statement macros from comp.lang.c faq, http://c-faq.com/cpp/multistmt.html

Regardless of whether the DEBUG_LOG uses { } or do while, it really should have an equal number of opening and closing currly brackets.

It is also definitely worth mentioning the NS_REQUIRES_NIL_TERMINATION macro!
Heh. Whoops. Thanks Peter! I've fixed all three now.
A drawback of using do/while is that your macro can't return a value. (I also find do/while in macros aesthetically unpleasant, but that's just me.) An alternative is to use GCC's multi-statement expressions: http://gcc.gnu.org/onlinedocs/gcc-4.4.1/gcc/Statement-Exprs.html#Statement-Exprs . This lets you do things like:


#define ARRAY_DEBUG(...) ({
    NSArray *_a = [NSArray arrayWithObjects: __VA_ARGS__];
    if (gDebug)
        dumpArray(_a);
    _a;
})

...

NSArray *myArray = ARRAY_DEBUG(@"foo", @"bar", nil);


The downside is of course that this is non-portable. I have no idea whether or not clang supports this extension.


On a different note, one very common use for vararg macros is to abbreviate nil-terminated vararg functions. Eg:


#define ARRAY(...) [NSArray arrayWithObjects: __VA_ARGS__, nil]


This has the drawback of not working for empty arrays, but there are easier ways to create those. Jens Alfke's utilities may be better https://bitbucket.org/snej/myutilities/src/tip/CollectionUtils.h

It's likely that clang supports such devices as well. Their stated goal is to support nearly all gcc language extensions so that clang is able to build nearly all existing gcc-targeted code.

Your ARRAY macro ought to work for empty arrays as well, but you have to pass an explicit nil parameter instead of just leaving the list empty. If you really wanted to you could pair it with a custom vararg function/method that allowed an empty parameter list in the macro.
There's probably a missing ',' here:

fprintf(stderr, "Debug log:" __VA_ARGS__); \
Definitely not missing a comma. Imagine this usage of the macro:

DEBUG_LOG("the answer is %d", fortyTwo);

With the comma, the line would expand to:

fprintf(stderr, "Debug log:", "the answer is %d", fortyTwo);

With the result that it will print only the text, "Debug log:", and nothing else.

Without the comma, it expands to this:

fprintf(stderr, "Debug log:" "the answer is %d", fortyTwo);

The adjacent string literals get concatenated, and so it prints, "Debug log:the answer is 42".

It ought to have a space after the colon, though....

Comments RSS feed for this page

Add your thoughts, post a comment:

Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.

Name:
The Answer to the Ultimate Question of Life, the Universe, and Everything?
Comment:
Formatting: <i> <b> <blockquote> <code>.
NOTE: Due to an increase in spam, URLs are forbidden! Please provide search terms or fragment your URLs so they don't look like URLs.
Code syntax highlighting thanks to Pygments.
Hosted at DigitalOcean.