>On Tue, 05 Apr 2005 11:45:57 +0530, grid
<pr******@gmail.com> wrote: I have a function which takes a variable number of arguments.It then
calls va_start macro to initialize the argument list.But here in my case
I have a special builtin va_start provided by the compiler which I use
for performance gains.
It is not a "performance" item, but rather a correctness issue.
Using the __builtin_* functions will do the right thing; trying
to fake it out by hand will not, in some cases.
In any case, the __builtin_* stuff is hidden behind macros, and
you, as the programmer, should just use the advertised interface
(va_start, va_arg, va_end, and in this case, nothing else).
Then the variable arguments are passed to another varargs
function which then uses a builtin va_arg macro to get the arguments.
This is allowed, but *only* via this form:
SOMETYPE like_printf(const char *fmt, va_list ap) {
/* code that uses va_arg(ap, TYPE) */
}
(of course SOMETYPE can be any valid function-return type, the name
of the function can be any valid function name, the "fmt" argument
can be something else, and so on). The important point here is
that "ap" is a formal parameter of type "va_list". If va_list is
a typedef for an array type, C's peculiar treatment of arrays will
automatically adjust it to be "pointer to first element of that
array" instead (as is happening here).
In article <sl******************@ccserver.keris.net>
Chris Croughton <ch***@keristor.net> wrote:For a start, I am very dubious that there are any significant
performance gains in doing that. If there were, the library
implementors should have defined the macrose in terms of the builtins in
the first place.
You are right, and they did. (I am gazing into my crystal ball and
I see that "grid" is using a PowerPC or similar architecture.)
It doesn't seem to be. C99 says that you can pass it as an argument to
a function, and to va_start/va_arg/va_end/va_copy, but the existence of
va_copy() would seem to imply that just assigning it is not a defined
behaviour
This is correct. In particular, on the machine he has, the contents
of <stdarg.h> include something like this:
typedef struct __builtin_va_list_struct_type va_list[1];
struct __builtin_va_list_struct_type {
int __ni; /* number of saved int regs */
int __i[__VA_MAXI]; /* and their values */
int __nf; /* number of saved FPU regs */
float __f[__VA_MAXF]; /* and their values */
int *__rest; /* other values (on stack) */
};
(gcc/glibc seems to accept it, but that doesn't imply that
anything else would).
The machine on which va_copy is a simple copy, by contrast, has this
in its <stdarg.h>:
typedef char *va_list;
Note the (second) error message here:
gs.c(749): warning #563: initialization with "{...}" expected for
aggregate object
va_list ap = arglist;
gs.c(749): error: a value of type "struct __builtin_va_list_struct_type
*" cannot be used to initialize an entity of type "struct
__builtin_va_list_struct_type"
va_list ap = arglist;
^
Clearly "arglist" has type "pointer to struct ...", while "ap" needs
an initializer of type "struct ...", enclosed in braces. Clearly
"ap" is actually an array type, while "arglist" is a pointer type.
How can "arglist" be a pointer type when it also uses "va_list"
as its declaration? The answer lies in The Rule about arrays and
pointers in C.
Let me show the two lines in question one more time here:
int gsScanfV(const char *fmt, va_list arglist) {
va_list ap = arglist;
...
}
If va_list is a typedef for an array type (and it is), we now
have the same thing we would get with:
void f(char arg_s[1]) {
char local_s[1] = arg_s;
...
}
Here arg_s has type "char *" (pointer to char), while local_s has
type "char [1]" (array 1 of char). The initializer is invalid
and a diagnostic must occur.
I could solve the above compilation error by changing the "va_list
arglist " to "va_list ap" in the gsScanfV() function parameters and
removing the assignmet line (va_list ap = arglist;).
This is a good solution *provided* it is OK to destroy the va_list
object whose element-address has been passed (courtesy, again, of
The Rule). Consider for instance:
int message(const char *fmt, ...) {
va_list ap;
int ret;
va_start(ap, fmt);
ret = vfprintf(stdout, fmt, ap);
va_end(ap);
return ret;
}
This function works just like printf(). The vfprintf() function,
which receives the address of the first (and only) element of the
"struct" containing the various registers, is allowed to modify
ap[0].__ni (AKA ap->__ni) and ap->__nf and ap->__rest. Not only
is this allowed, it actually happens. Now suppose we decide that
message() should not only print to stdout, but also to a message-file.
So we try change message() to read:
int message(const char *fmt, ...) {
va_list ap;
int ret;
extern FILE *logfile;
va_start(ap, fmt);
ret = vfprintf(stdout, fmt, ap);
ret = vfprintf(logfile, fmt, ap); /* OOPS! WRONG! BUG! */
va_end(ap);
return ret;
}
As before, vfprintf() -- the first call, to stdout -- modifies
ap->__ni and ap->__nf and ap->__rest. The second call receives
these modified values -- and prints junk.
On another machine, where "va_list" is just a typedef for "char *",
the first vfprintf() modifies a *copy* of (the entire value of)
ap, instead of the (single) array element ap[0]. The second
vfprintf() gets another new, fresh copy, and it all works.
In other words, this bug-ridden modified message() function works
on some machines, and fails on others -- just what one can expect
of code with undefined behavior.
One fix, and the only one available in C89, is to rewrite
message() itself:
int message(const char *fmt, ...) {
va_list ap;
int ret;
extern FILE *logfile;
va_start(ap, fmt);
ret = vfprintf(stdout, fmt, ap);
va_end(ap);
va_start(ap, fmt);
ret = vfprintf(logfile, fmt, ap); /* OK this time */
va_end(ap);
return ret;
}
The first va_end cleans up, and the second va_start "resets" the
array contents, so that the second vfprintf() works.
This is all well and good until we go to write the missing vmessage()
function, a la fprintf() and vfprintf(). Our mesage() has the
actual "fmt, ..." parameters and can use va_start() twice -- but
vmessage() should read something like:
int vmessage(const char *fmt, va_list ap) {
extern FILE *logfile;
(void) vfprintf(stdout, fmt, ap);
return vfprintf(logfile, fmt, ap); /* OOPS! WRONG! BUG! */
}
Here we have the same problem as last time: we destroy the values
in ap[0].__ni, ap[0].__nf, and so on, so that the second vfprintf()
prints junk. But in C89, there is no way to fix it. We can
instead require two copies of the va_list:
int ugly_vmessage(const char *fmt, va_list ap1, va_list ap2) {
extern FILE *logfile;
(void) vfprintf(stdout, fmt, ap1);
return vfprintf(logfile, fmt, ap2); /* OK now */
}
which requires the caller to do two va_start()s. This is the only
thing we *can* do. In C99, however, we have the new va_copy macro,
so we can fix it the other way:
int vmessage(const char *fmt, va_list ap) {
extern FILE *logfile;
va_list copy;
va_copy(copy, ap);
(void) vfprintf(stdout, fmt, copy);
va_end(copy); /* see note below */
return vfprintf(logfile, fmt, ap); /* OK now */
}
Now vmessage() still destroys its "va_list" argument on the way
out, but it first makes a copy and uses that to print to stdout.
Note, my C99 draft standard implies that the above va_end() call
is required -- that a va_copy() has a sort of implied va_start()
-- although this is just from an example and not from the text
itself. I would check the final standard before assuming this is
correct.
Without seeing all the code for gsScanfV() and its caller(s),
I cannot say whether a va_copy() is required. (My crystal ball
is a bit cloudy.)
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: forget about it
http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.