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
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, ...)
__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)
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();
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)
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)
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 NSNotification
s 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 */)
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);
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 *);
}
va_end(args);
}
PostNotifications(self, FirstNotificationName, SecondNotificationName, ThirdNotificationName, nil);
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!
Comments:
This causes problems (again, IIRC) on 64-bit systems.
http://www.mikeash.com/?page=pyblog/friday-qa-2009-07-17-format-strings-tips-and-tricks.html
#define FOO { method1(); method2(); }
IMO this is idential to the while construct in every aspect (variable scope, ...).
http://cocoawithlove.com/2009/05/variable-argument-lists-in-cocoa.html
Worth a read.
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.
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.
It is also definitely worth mentioning the NS_REQUIRES_NIL_TERMINATION macro!
#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
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.
fprintf(stderr, "Debug log:" __VA_ARGS__); \
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.
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.