Variable argument lists in C

| | Comments (9)

So I've been plagued by a concept for a while, and finally decided to take the time to solve the issue. The problem was that I had a function which took a variable argument list, and I wanted to pass that list along to another function which took a variable argument list.

For instance, I wanted to write a function that would take a variable list, use sprintf to print the list into a buffer, and then call OutputDebugString to print it out. The trouble is -- how do you pass the variable argument list on?

For starters, let's peek at the first attempt of our C function


static void DebugLog( const char *msg, ... )
{
char buffer[1024] = { 0 };
sprintf_s( buffer, _countof( buffer ), msg, ???? );

::OutputDebugStringA( buffer );
}

For those of you who need a refresher course, the ... in the function declaration is C's way of specifying a variable argument list (or ParamArray, in RB). The question is: what do you put in place of the ???? to make the call to sprintf_s work properly?

For starters, you cannot simply pass ... in there -- it simply will not compile. Which makes sense as ... is simply a magical place holder for a va_list, of sorts.

So that got me thinking -- perhaps I could iterate over the list of arguments (as a va_list) and somehow rebuild the list. However, that leaves the same problem: what do you pass? Then I realized that ... was just a special form of va_list, so perhaps I could simply declare the function as taking a va_list, like this:


static void DebugLog( const char *msg, va_list list )

and then pass list along to sprintf_s. The code would compile by itself, but when I attempted to actually *use* the va_list by calling DebugLog, it would no longer compile. That also makes sense because the method's signature only has two parameters, so anything beyond two parameters is not legal.

Ho hum. Then it dawned on me -- I simply needed to convert the ... into a va_list, then I could use the variadic version of sprintf_s! You see, there is another form of sprintf_s, called vsprintf_s, whose last parameter receives a va_list. And it never dawned on me before that this was how to pass along a variable arguments list in C. So now it was just a simple matter of turning ... into a va_list, which is something trivial (using va_start). So now the function looks like this:


static void DebugLog( const char *msg, ... )
{
va_list va;
va_start( va, msg );

char buffer[1024] = { 0 };
vsprintf_s( buffer, _countof( buffer ), msg, va );

va_end( va );

::OutputDebugStringA( buffer );
}


And it works like a charm. So that's how you pass along a variable arguments list -- declare the second function as receiving a va_list and you can use this paradigm to pass the variable arguments along.

Neat, eh?

9 Comments

I'm so glad I don't have to write code in C++, LOL :P

Amen to that. Wow, my C is rusty. :D

Wow... I'm shocked that my C++ teachers taught me none of this. I have no clue was a va_list is, nor what sprintf is suppose to do. I think I stick to RB for a while, now. That is until I have to take some assembly classes next semester ;)

LOL... This is chinese to me ;-)

Like I said, this is a rather arcane area of C (not C++, just so you know) -- but it may come in handy for some people to have this spelled out nicely for them.

There are times I miss coding in C. This doesn't compel me to get back into it, but it does bring back the good ol' days when I used to do it (two months ago). I do really miss the Flex/Bison stuff from compilers; I never bothered much with va_lists, as none of what I did required any fancy-schmancy stuff like that.

As much as I liked C, I'm too much of a high-level coder to spend my life in the C trenches. Besides, *someone's* gotta do WP->MT conversions, right? ;)

Talk about bringing back the bad old days when I wrote C for 15 hours a day :)

va_list, va_start, and va_end are all quite familiar as I used them a lot in a previosu life where I wrote a SQL engine for a non-SQL database.

Nasty stuff and RB makes this so much easier to deal with.

I'm glad it's you and not me :)

And that's why, if you're creating an API, whenever you create a varargs function foobar(...) you should ALWAYS create a vfoobar(va_list) version too. The vector version is necessary for anyone who might want to wrap one varargs function with another.

It's not impossible to pass an ellipsis (...) argument set to another function without using va_list, but it's a huge pain and usually requires platform-specific assembly code with intimate knowledge of how the stack works. It's almost never worth the effort to do so.

@Drew -- I totally agree; but thankfully, the only APIs make don't use variable argument lists in the traditional C sense. :-)

Leave a comment

Disclaimer

I'm currently an employee of REAL Software. My blog is mine. The opinions represented in this blog are mine as well and may not represent my employer's opinions. All original material is copyrighted and property of the author.

REALbasic® is a registered trademark of REAL Software, Inc. REAL SQL Server™ and Lingua™ are pending trademarks of REAL Software, Inc. All rights reserved.