Using stdarg with unknown types

Discussion in 'C Programming' started by Clint Olsen, Oct 27, 2003.

  1. Clint Olsen

    Clint Olsen Guest

    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
     
    Clint Olsen, Oct 27, 2003
    #1
    1. Advertisements

  2. Clint Olsen

    Chris Torek Guest

    Not possible in general. Also, due to ANSI's wacky widening rules,
    surprisingly hard to do even in specific, sometimes. :)
    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.)
     
    Chris Torek, Oct 27, 2003
    #2
    1. Advertisements

  3. I must be missing something. Do the stdarg.h facilities not work
    for floating point arguments?
     
    Sheldon Simms, Oct 27, 2003
    #3
  4. Clint Olsen

    Dan Pop Guest

    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
     
    Dan Pop, Oct 27, 2003
    #4
  5. Clint Olsen

    Clint Olsen Guest

    [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
     
    Clint Olsen, Oct 27, 2003
    #5
  6. Clint Olsen

    Chris Torek Guest

    I wrote, in part:
    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.]
    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 :) ).
     
    Chris Torek, Oct 29, 2003
    #6
  7. <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...
     
    Sheldon Simms, Oct 29, 2003
    #7
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.