#define va_start(ap,arg) ((ap) = (char *) &arg) /* ? */
No -- the idea is to take the address of the "variable" named "..."
in something like:
void f(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
/* code here */
va_end(ap);
}
#define va_arg(ap,type) ((type*)(ap+=sizeof(type)))[-1]
#define va_end(ap)
Try to figure out how to modify that without using any extensions to C
such as sizeof(promoted_typeof(type)). ...
The 'extension' is static type analysis, indeed, the *same* analysis
used to promote parameters in variadic function calls in the first
place.
Yes, but while this is available to the compiler (internally),
there is no existing C construct that requires that it be exposed
to the programmer. A __sizeof_promoted_type operator *ought* to
be trivial to add (because the compiler has to know how to do this
inside), but it does have to be added. (It is slightly worse than
this; see the end of this article.)
All that said, back in 1989, the X3J11 committee actually changed
the way one used what was then <varargs.h>, adding some required
explicit syntax -- the ", ..." part of a prototype -- and changing
the va_start() macro so that it takes two arguments instead of one.
Having done that, I always thought it was silly not to go the rest
of the way, and make "..." act like a special purpose variable:
#define va_start(ap) ((ap) = &...)
While va_start() could be retained (from <varargs.h> in the new
<stdarg.h>), the C committee could have written that this is the
one and only correct way to #define it. The "variable" named ...
would thus become a non-modifiable, indescribably-typed lvalue
whose only allowed operation is "take its address", producing a
value of whatever type "va_list" is.
Note that this -- removing the second argument to va_start() --
neatly expresses the constraint C imposes, that there only be a
single set of varying arguments. Having that second argument
implies (inappropriately) that one might be able to start the
varying portion at different points. Furthermore, with this "&..."
change, the requirement for at least one fixed argument could also
be dropped, giving us the syntax:
void g(...) {
va_list ap;
ap = &...; /* inline expansion of va_start(ap) */
/* code here */
va_end(ap);
}
This kind of function makes sense in some contexts, e.g., when
the body is something like:
char *p, *ret;
size_t len = 0;
while ((p = va_arg(ap, char *)) != NULL)
len += strlen(p);
va_end(ap);
ret = malloc(len + 1);
if (ret != NULL) {
char *tail = ret;
va_start(ap);
while ((p = va_arg(ap, char *)) != NULL) {
/* can do this in one line with sprintf, but is slow */
strcpy(tail, p);
tail += strlen(tail);
}
va_end(ap);
*tail = '\0'; /* in case there are no strings */
}
and of course a final "return ret".
The issue of whether to require promoted types to va_arg() could
remain separate. Requiring implementations to provide a "size of
promoted type" keyword, on implementations that use something as
simple as the va_arg() macro described above, is not much of a
hardship -- and for implementations that use "secret" compiler
tricks to access the varying arguments, doing the promotion is
still not a hardship. I for one think it would be an improvement:
not necessarily an *important* one, but in some ways, a "good"
language is one in which all the minor irritants have been removed
wherever feasible. One of J&W Pascal's original nasty failings
was the lack of a default case. Yes, you can work around it with
an "if" in front of the switch/case -- but this is a pesky thorn,
and there is no reason not to remove it from the lion's foot.
Assuming we *were* to add a special new keyword or two, here is
one way might rewrite va_arg:
/* original:
va_arg(ap, ty) (((ty *)(ap += sizeof(ty)))[-1])
new: */
#define va_arg(ap, ty) ((ty)((__convert_to_ptr_to_promoted(ty, \
ap += __sizeof_promoted(ty)))[-1]))
This also removes the pesky thorn that the "type" parameter to
va_arg be syntactically valid after suffixing with "*".
