Using stdarg with unknown types

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Clint Olsen

    Using stdarg with unknown types

    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
  • Chris Torek

    #2
    Re: Using stdarg with unknown types

    In article <slrnbpplpq.ma4 .clint@poly.0ls en.net>
    Clint Olsen <clint@0lsen.ne t> writes:[color=blue]
    >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] ...[/color]

    Not possible in general. Also, due to ANSI's wacky widening rules,
    surprisingly hard to do even in specific, sometimes. :-)
    [color=blue]
    >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?[/color]

    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.)
    --
    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://67.40.109.61/torek/index.html (for the moment)
    Reading email is like searching for food in the garbage, thanks to spammers.

    Comment

    • Sheldon Simms

      #3
      Re: Using stdarg with unknown types

      On Mon, 27 Oct 2003 02:36:07 -0700, Chris Torek wrote:
      [color=blue]
      > In article <slrnbpplpq.ma4 .clint@poly.0ls en.net>
      > Clint Olsen <clint@0lsen.ne t> writes:[color=green]
      >>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...[/color][/color]
      [color=blue]
      > 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?[/color]

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


      Comment

      • Dan Pop

        #4
        Re: Using stdarg with unknown types

        In <pan.2003.10.27 .14.24.10.59777 8@yahoo.com> Sheldon Simms <sheldonsimms@y ahoo.com> writes:
        [color=blue]
        >On Mon, 27 Oct 2003 02:36:07 -0700, Chris Torek wrote:
        >[color=green]
        >> In article <slrnbpplpq.ma4 .clint@poly.0ls en.net>
        >> Clint Olsen <clint@0lsen.ne t> writes:[color=darkred]
        >>>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...[/color][/color]
        >[color=green]
        >> 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?[/color]
        >
        >I must be missing something. Do the stdarg.h facilities not work
        >for floating point arguments?[/color]

        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
        DESY Zeuthen, RZ group
        Email: Dan.Pop@ifh.de

        Comment

        • Clint Olsen

          #5
          Re: Using stdarg with unknown types

          On 2003-10-27, Chris Torek <nospam@elf.eng .bsdi.com> wrote:[color=blue]
          >
          > Not possible in general. Also, due to ANSI's wacky widening rules,
          > surprisingly hard to do even in specific, sometimes. :-)[/color]

          [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

          Comment

          • Chris Torek

            #6
            Re: Using stdarg with unknown types

            I wrote, in part:
            [color=blue][color=green]
            >> [Assume you are on, e.g., a 64-bit MIPS or SPARC, so that[/color][/color]
            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.][color=blue][color=green]
            >> Inside [a proposed function] foo(), all you have is the number 8.
            >> Were the parameters passed via the integer registers, or in the FPU?[/color][/color]

            In article <pan.2003.10.27 .14.24.10.59777 8@yahoo.com>,
            Sheldon Simms <sheldonsimms@y ahoo.com> wrote:[color=blue]
            >I must be missing something. Do the stdarg.h facilities not work
            >for floating point arguments?[/color]

            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 :-) ).
            --
            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://67.40.109.61/torek/index.html (for the moment)
            Reading email is like searching for food in the garbage, thanks to spammers.

            Comment

            • Sheldon Simms

              #7
              Re: Using stdarg with unknown types

              On Tue, 28 Oct 2003 21:36:49 -0700, Chris Torek wrote:
              [color=blue]
              > I wrote, in part:
              >[color=green][color=darkred]
              >>> [Assume you are on, e.g., a 64-bit MIPS or SPARC, so that[/color][/color]
              > 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.][color=green][color=darkred]
              >>> Inside [a proposed function] foo(), all you have is the number 8.
              >>> Were the parameters passed via the integer registers, or in the FPU?[/color][/color]
              >
              > In article <pan.2003.10.27 .14.24.10.59777 8@yahoo.com>,
              > Sheldon Simms <sheldonsimms@y ahoo.com> wrote:[color=green]
              >>I must be missing something. Do the stdarg.h facilities not work
              >>for floating point arguments?[/color]
              >
              > They do -- but compare an actual viable foo1() with the proposed
              > foo2():[/color]

              <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...


              Comment

              Working...