6

I'm writing a function in C that takes a variable number of arguments.

size_t myprintf(char *fmt, ...);

So far, so good. I've decided it's best to do things the Right Way™ and make a version that takes variable arguments, and another version that takes a va_list.

size_t myprintf(char *fmt, ...);
size_t myvprintf(char *fmt, va_list args);

Not that hard to do. Except my_vprintf() needs to send its args out to two different functions (first to snprintf() with a length of 0 to determine how much room we need, then to sprintf() after we've allocated that much room). I do this with va_copy.

size_t myvprintf(char *fmt, va_list args)
{
    va_list args2;
    va_copy(args, args2);
    // do stuff with args2
    va_end(args2);
    // do more stuff with args
}

This is all fine and dandy, but C99 is a bit poorly implemented. I would like, if possible, for my code to work in C89 as well, and to work with as many compilers and on as many platforms as possible. I currently have this after #include <stddef.h> but before any code:

#ifndef va_copy
# ifdef __va_copy
#  define va_copy(a,b) __va_copy(a,b)
# else /* !__va_copy */
#  define va_copy(a,b) ((a)=(b))
# endif /* __va_copy */
#endif /* va_copy */

I am led to believe that ((a)=(b)) is unreliable, and that I should perhaps use memcpy() or something similar, but this is still on the level of "If you don't support C99, I hope it works" rather than "If you don't support C99, never fear" (which is what I want). Is there any good way to get around this limitation? I've seen a few solutions - va_list functions that eat one argument and recurse, passing the va_list twice so that two separate copies are made, etc. - but I don't know how well they would work (and the recursive solution won't do so well if I just want to call vsnprintf(), now, will it?).

So I turn to you, StackOverflow User. Is there anything more I can do to provide C89 compatibility, or are users without va_copy and __va_copy (admittedly few and far between) just going to have to suck it in and take it?

Chris Lutz
  • 73,191
  • 16
  • 130
  • 183
  • It's 2009. Why you still need C89 compatibility? – stepancheg Aug 11 '09 at 09:26
  • Because VC++ doesn't (and probably won't) support it. And I like to have it. I think it's a nice thing to have. – Chris Lutz Aug 11 '09 at 09:35
  • I think MS have actually gone as far as on record to say they have no intention to go for C99, except to pick and choose the odd features from it that their customers want. – KTC Aug 11 '09 at 09:50
  • Sounds like Microsoft. I would say they should pick proper varargs support, but I'm not technically one of their customers. I suppose it makes sense that they won't support C99, since VC++ is a C++ compiler and not a C compiler, but we can still dream. – Chris Lutz Aug 11 '09 at 10:04
  • 3
    It's not just Microsoft. Loads of compilers don't support C99. Including gcc (although it's nearly there, and has varags covered). If you want to write standard C with any portability at all, use C89. Or invent your own "subset of C99 supported by all the compilers I actually care about". – Steve Jessop Aug 11 '09 at 10:21

1 Answers1

3

(a)=(b) is unreliable. Even passing two va_list is (the public function would be a simple wrapper) as va_list can be something like:

typedef __va_list_impl va_list[1];

for which doing a va_arg on one will be modify the other (I seem to remember that Solaris use such kind of thing, ah, the register windows...). Sadly, I know of no sure way to do what you want.

AProgrammer
  • 51,233
  • 8
  • 91
  • 143
  • 1
    I was afraid of this. Oh, well. I suppose the appropriate thing to do would be to change my hack-`va_copy` to `#error "Get a new compiler"` – Chris Lutz Aug 11 '09 at 09:47
  • 1
    In this particular case the assignment in the va_copy macro would fail, since you can't assign one array to another. – caf Aug 11 '09 at 11:50
  • @ChrisLutz You forgot the exclamation mark in `"Get a new compiler!"` :P. – S.S. Anne Aug 31 '19 at 01:33