Using stdarg with unknown types

C

Clint Olsen

I had this crazy idea to use stdarg macros to copy data in a generic
fashion, regardless of type. However, I'm not sure it will work in part
due to the ANSI C default argument promotions and the fact that va_arg
requires a type to decide how far to advance to subsequent arguments. So,
essentially what I want in the variable argument function is something that
just assigns to a character type, and then copy a predefined number of
bytes to a target location (using memcpy or whatever).

Example:

void foo(int numargs, size_t size, ...)

Usage:

/*
* Must use in type due to default argument promotions
*
foo(3, sizeof(int), 'b', 'a', 'r');

or:

/*
* Bar is some defined type and bar1..4 are variables of that type
*
foo(4, sizeof(Bar), bar1, bar2, bar3, bar4);

Any suggestions?

Thanks,

-Clint
 
C

Chris Torek

I had this crazy idea to use stdarg macros to copy data in a generic
fashion, regardless of type. However, I'm not sure it will work in part
due to the ANSI C default argument promotions and the fact that va_arg
requires a type to [examine] ...

Not possible in general. Also, due to ANSI's wacky widening rules,
surprisingly hard to do even in specific, sometimes. :)
So, essentially what I want in the variable argument function is
something that just assigns to a character type, and then copy a
predefined number of bytes to a target location (using memcpy or
whatever).

Example:

void foo(int numargs, size_t size, ...)

Usage:

/*
* Must use in type due to default argument promotions
*
foo(3, sizeof(int), 'b', 'a', 'r');

or:

/*
* Bar is some defined type and bar1..4 are variables of that type
*
foo(4, sizeof(Bar), bar1, bar2, bar3, bar4);

Any suggestions?

This fails for "typedef float Bar;" on a number of existing
implementations, which pass "int"s in integer registers and
floating-point values in floating-point registers. And of course,
"float"s are widened to "double"s, so just as you had to use
sizeof(int) in the first call, the sizeof(Bar) in the second
produces the wrong number (4 instead of 8).

Even if you fix the latter -- by defining, for instance, a name
for "promoted Bar" -- the former remains a problem. Suppose I
wish to call foo() with some values of type "long" on one of
these machines, which happens to have sizeof(long) == 8. (Note
that sizeof(double) is still 8.) Then:

foo(3, sizeof(long), 0L, 99L, 1L << 53);

passes three "long"s in integer registers -- actually, on at least
one of these machines, only two of them go in the integer registers
and the third is on a stack, but then I have to use "long long" to
make the example fly -- while:

foo(4, sizeof(Promoted_Bar), bar1, bar2, bar3, bar4);

passes the four "double"s in floating-point registers.

Inside foo(), all you have is the number 8. Were the parameters
passed via the integer registers, or in the FPU?

Note that the process gets particularly ugly for narrow types that
might or might not be unsigned and might or might not widen from
(say) 16 to 32 bits, or stay at 32 bits. For instance, suppose
you are using a POSIX header and the type "uid_t". Is uid_t
"unsigned short", or is it a plain short or an int or an unsigned
int? (The answer is different on different hosts; you will find
both results.) If it is unsigned short, and short is 16 bits and
int is 32 bits, it widens to signed int; but if it is unsigned int,
it stays unsigned int. If you needed portability to PDP-11s and
uid_t were unsigned short there, the 16-bit unsigned short would
widen to unsigned int.

(In practice, of course, unsigned and signed "int"s are always
passed the same way on these machines. The integer-vs-FP register
issue also only occurs if you allow floating-point parameters.
Thus, if you are willing to give up broad "pure" portability and
a certain amount of practical functionality, you can get away with
the trick you are aiming for. But at this point you might as well
not write it in C, or at least, not claim that the C is even remotely
portable -- just require a new implementation for each port, and
hope you never come across an un-handle-able situation like the
integer-vs-FP-registers one.)
 
S

Sheldon Simms

Suppose I
wish to call foo() with some values of type "long" on one of these
machines, which happens to have sizeof(long) == 8. Then:

foo(3, sizeof(long), 0L, 99L, 1L << 53);

passes three "long"s in integer registers -- actually, on at least one
of these machines, only two of them go in the integer registers and the
third is on a stack, but then I have to use "long long" to make the
example fly -- while:

foo(4, sizeof(Promoted_Bar), bar1, bar2, bar3, bar4);

passes the four "double"s in floating-point registers.

Inside foo(), all you have is the number 8. Were the parameters passed
via the integer registers, or in the FPU?

I must be missing something. Do the stdarg.h facilities not work
for floating point arguments?
 
D

Dan Pop

In said:
I must be missing something. Do the stdarg.h facilities not work
for floating point arguments?

They do, but they *know* the argument type, therefore they know where to
look for it: in a GPR, in an FPR or somewhere else.

Dan
 
C

Clint Olsen

Not possible in general. Also, due to ANSI's wacky widening rules,
surprisingly hard to do even in specific, sometimes. :)

[excellent description of caveats deleted]

Chris:

Thanks a lot for the explanation. It sounds like using void pointers and
memcpy would be the only way to do this portably. It's too bad there's not
a clean(er) way to handle variable argument lists in C...

-Clint
 
C

Chris Torek

I wrote, in part:
[Assume you are on, e.g., a 64-bit MIPS or SPARC, so that
sizeof(long) and sizeof(double) are both 8, while "long"s are
passed in integer registers (a0 through a3 on MIPS, %o0 through
%o5 on SPARC) and "double"s are passed in FPU registers.]
Inside [a proposed function] foo(), all you have is the number 8.
Were the parameters passed via the integer registers, or in the FPU?

I must be missing something. Do the stdarg.h facilities not work
for floating point arguments?

They do -- but compare an actual viable foo1() with the proposed
foo2():

void foo1(const char *fmt, ...) {
va_list ap;

va_start(ap, fmt);
/* as usual the real work is in a v* variant */
vfoo1(fmt, ap);
va_end(ap);
}

void vfoo1(const char *fmt, va_list ap) {
char c;
long l;
double d;

while ((c = *fmt++) != '\0') {
switch (c) {
case 'l':
l = va_arg(ap, long);
... handle long ...
break;
case 'f': case 'd':
d = va_arg(ap, double);
... handle double ...
break;
... more cases here as needed ...
}
}
}

In vfoo1(), we call va_arg() with the name of a type: "int" or
"double". Here is foo2() and the start of vfoo2():

void foo2(int nargs, size_t size, ...) {
va_list ap;

va_start(ap, size);
vfoo2(nargs, size, ap);
va_end(ap);
}

void vfoo2(int nargs, size_t size, va_list ap) {
char c;
long l;
double d;
int argno;

for (argno = 0; argno < nargs; argno++) {
switch (size) {
case 8:
... now what? ...
...
}
}
}

What code would you put under "case 8"?

Note that you can write *either*:

case sizeof(long):

*or*:

case sizeof(double):

instead of the plain "case 8", but using the same number for two
"case"s in a single switch, is a constraint violation, so using
both will produce a compile-time diagnostic (which I suppose is a
good thing here :) ).
 
S

Sheldon Simms

I wrote, in part:
[Assume you are on, e.g., a 64-bit MIPS or SPARC, so that
sizeof(long) and sizeof(double) are both 8, while "long"s are
passed in integer registers (a0 through a3 on MIPS, %o0 through
%o5 on SPARC) and "double"s are passed in FPU registers.]
Inside [a proposed function] foo(), all you have is the number 8.
Were the parameters passed via the integer registers, or in the FPU?

I must be missing something. Do the stdarg.h facilities not work
for floating point arguments?

They do -- but compare an actual viable foo1() with the proposed
foo2():

<rest snipped>

Well I guess it's my turn to feel like an idiot. Hopefully
that will teach me to wait until after the coffee has had its
effect before posting.

Thanks for clarifying...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,053
Latest member
BrodieSola

Latest Threads

Top