[va_list *] Is this legal ?

Discussion in 'C Programming' started by m.labanowicz, Jan 16, 2013.

  1. m.labanowicz

    Shao Miller Guest

    I don't understand why you need to wrap 'va_list' at all, when you can
    use 'va_list *' parameters for all functions that are called by 'bar'.
     
    Shao Miller, Jan 17, 2013
    #21
    1. Advertisements

  2. m.labanowicz

    m.labanowicz Guest

    void fooB(va_list ap) {
    ....

    Please imagine that I'm implementator of function fooB that
    declaration can not be modified:

    void fooB(va_list ap);
     
    m.labanowicz, Jan 17, 2013
    #22
    1. Advertisements

  3. m.labanowicz

    Shao Miller Guest

    Aha. I sort of figured that. :)

    In that case, you can use 'va_copy', which is a portable macro. Then
    you can pass around a pointer to that copy!

    int get_int(va_list * pap) {
    return va_arg(*pap, int);
    }

    void fooB(va_list ap) {
    va_list ap_copy;

    va_copy(ap_copy, ap);
    get_int(&ap_copy);
    va_end(ap_copy);
    }
     
    Shao Miller, Jan 17, 2013
    #23
  4. m.labanowicz

    m.labanowicz Guest

    In that case, you can use 'va_copy', which is a portable macro. Then

    Yes, it is true, but:

    $gcc -W -Wall -ansi -pedantic test4.c
    test4.c:43:3: warning: implicit declaration of function ‘va_copy’ [-Wimplicit-function-declaration]
    /tmp/ccwjg0Bw.o: In function `bar':
    test4.c:(.text+0x2cf): undefined reference to `va_copy'

    'va_copy' is available from C99.

    I'm looking solution for C89.
     
    m.labanowicz, Jan 17, 2013
    #24
  5. m.labanowicz

    Shao Miller Guest

    C89 doesn't allow for a function with a 'va_list' parameter to pass that
    parameter to yet another function. It also doesn't specify anything
    about the 'va_list' type other than that it's a "type suitable for
    holding information need by the macros va_start, va_arg, va_end." It is
    also known to be an object type, because it states that 'ap' is an
    object. But because it could be an array type, a 'va_list' parameter
    could be either the same type as a 'va_list' declared outside of a
    function parameter list, or it could be a different type.

    The biggest problem I see with trying to make a pre-C89 'va_copy' or
    with trying to code a work-around so that you can work with 'ap' at any
    depth of call is that 'va_list' could be a const-qualified type, so you
    can't portably modify it.

    Having said that, I don't yet know if it's impossible. :)

    Is it really a major pain to do all your 'va_arg' work in the function
    with the 'va_list' parameter?
     
    Shao Miller, Jan 17, 2013
    #25
  6. m.labanowicz

    Shao Miller Guest

    Just for fun, if you can rely on 'va_list' being a type that's not
    const-qualified, you could experiment with the following code:

    #include <stddef.h>
    #include <stdarg.h>
    #ifdef USE_MEMCPY
    #include <string.h>
    #endif

    #define DECL_VA_BACKUP(backup, ap) \
    unsigned char \
    (backup)[sizeof (ap)], \
    (backup ## _) = va_backup( \
    (backup), \
    (const void *) &(ap), \
    (backup) + sizeof (ap), \
    &(backup ## _) \
    )
    #define VA_BACKUP(backup, ap) \
    ((void) va_backup( \
    (backup), \
    (const void *) &(ap), \
    (backup) + sizeof (ap) \
    ))
    #define VA_RESTORE(ap, backup) \
    ((void) va_backup( \
    (void *) &(ap), \
    (backup), \
    (void *) (&(ap) + 1) \
    ))

    typedef unsigned char va_copy_t;

    unsigned char va_backup(
    va_copy_t * dest,
    const va_copy_t * src,
    va_copy_t * const dest_end,
    ...
    ) {
    #ifndef USE_MEMCPY
    while (dest < dest_end)
    *dest++ = *src++;
    #else
    memcpy(dest, src, dest_end - dest);
    #endif
    return 0;
    }

    #include <stdio.h>
    #include <stdarg.h>

    int va_int(va_list ap, va_copy_t * ap_copy) {
    int i;

    VA_RESTORE(ap, ap_copy);
    i = va_arg(ap, int);
    VA_BACKUP(ap_copy, ap);
    return i;
    }

    double va_double(va_list ap, va_copy_t * ap_copy) {
    double d;

    VA_RESTORE(ap, ap_copy);
    d = va_arg(ap, double);
    VA_BACKUP(ap_copy, ap);
    return d;
    }

    void foo(va_list ap) {
    DECL_VA_BACKUP(ap_copy, ap);
    int i;
    double d;

    i = va_int(ap, ap_copy);
    d = va_double(ap, ap_copy);
    printf("i: %d, d: %f\n", i, d);
    }

    void caller(int parmN, ...) {
    va_list ap;

    va_start(ap, parmN);
    foo(ap);
    va_end(ap);
    }

    int main(void) {
    caller(0, 42, 3.14159);
    return 0;
    }
     
    Shao Miller, Jan 17, 2013
    #26
  7. But a C89 implementation (probably) wouldn't provide <stdint.h>.

    [...]
     
    Keith Thompson, Jan 17, 2013
    #27
  8. m.labanowicz

    m.labanowicz Guest

    Just for fun, if you can rely on 'va_list' being a type that's not
    ....

    Yes, it works.

    This is similar solution that has been presented previously.

    Regards
     
    m.labanowicz, Jan 17, 2013
    #28
  9. m.labanowicz

    Shao Miller Guest

    Right. And here's another just-for-fun (with the same limitation)
    that's a bit simpler. Obviously, the macros should only be used by
    functions that are more than one level deep from the first function
    having a 'va_list' as a parameter. (Deeper than 'foo', in this case.)

    #include <stddef.h>
    #include <stdarg.h>
    #ifdef USE_MEMCPY
    #include <string.h>
    #endif

    #define VA_COMMIT(addr, ap) \
    ((void) va_backup( \
    (addr), \
    (const void *) &(ap), \
    (unsigned char *) (addr) + sizeof (ap) \
    ))
    #define VA_RESTORE(ap, addr) \
    ((void) va_backup( \
    (void *) &(ap), \
    (addr), \
    (void *) (&(ap) + 1) \
    ))

    typedef void * va_laddr;

    unsigned char va_backup(
    unsigned char * dest,
    const unsigned char * src,
    unsigned char * const dest_end,
    ...
    ) {
    #ifndef USE_MEMCPY
    while (dest < dest_end)
    *dest++ = *src++;
    #else
    memcpy(dest, src, dest_end - dest);
    #endif
    return 0;
    }

    #include <stdio.h>
    #include <stdarg.h>

    int va_int(va_list ap, va_laddr pap) {
    int i;

    VA_RESTORE(ap, pap);
    i = va_arg(ap, int);
    VA_COMMIT(pap, ap);
    return i;
    }

    double va_double(va_list ap, va_laddr pap) {
    double d;

    VA_RESTORE(ap, pap);
    d = va_arg(ap, double);
    VA_COMMIT(pap, ap);
    return d;
    }

    void foo(va_list ap) {
    int i;
    double d;

    i = va_int(ap, &ap);
    d = va_double(ap, &ap);
    printf("i: %d, d: %f\n", i, d);
    }

    void caller(int parmN, ...) {
    va_list ap;

    va_start(ap, parmN);
    foo(ap);
    va_end(ap);
    }

    int main(void) {
    caller(0, 42, 3.14159);
    return 0;
    }

    I don't think that the problem was really that using 'va_list *' wasn't
    portable, it was that you weren't able to use it from the function with
    the ellipsis. The problem with using it from a downstream function
    seems to me to be the same as for any other function in C: You can't
    access the caller's object without some sort of a reference to it.
    Accessing the 'va_list' parameter in your 'fooB' is one step too late;
    you want to access the original 'va_list' object in your 'bar'.
     
    Shao Miller, Jan 17, 2013
    #29
  10. (snip)
    (snip, then I wrote)
    48 bits should last for a while.

    VAX and IBM S/370 both use a two level virtual addressing system.
    IBM uses segment table and page tables, VAX uses pagable page tables.

    IBM's 64 bit z/Architecture allows for five levels of tables, but with
    special options to allow for fewer levels until more bits of address
    are needed. Saves some time resolving address not in the TLB.

    So, there might be some use for signed pointers.

    -- glen
     
    glen herrmannsfeldt, Jan 18, 2013
    #30
  11. (snip, I wrote)
    (and also wrote)
    z/Linux probably just splits the space in two parts.

    z/OS has to stay compatible with programs written over 40 years ago.
    OS/360 stores other data in the high byte of 24 bit addresses in many
    control blocks. When it had to fit in 20K bytes on a 64K byte machine,
    that might have made sense. And z/OS doesn't use at all the space
    between 2GB and 4GB, that is, between the 31 and 32 bit lines.

    As I understand it, both the PL/I (F) compiler, and programs generated
    by it, still run under z/OS.

    -- glen
     
    glen herrmannsfeldt, Jan 18, 2013
    #31
  12. [...]

    This program, whose behavior is blatantly undefined, might provide some
    interesting information about how the compiler treats pointer values.

    #include <stdio.h>
    #include <stdint.h>
    int main(void) {
    if ((void*)(uintptr_t)-1 < (void*)(uintptr_t)0) {
    puts("pointers are treated as signed");
    }
    else {
    puts("pointers are treated as unsigned");
    }
    return 0;
    }

    (I get "pointers are treated as unsigned" on x86, x86_64, and SPARC.)
     
    Keith Thompson, Jan 18, 2013
    #32
  13. m.labanowicz

    Tim Rentsch Guest

    This conclusion is exactly backwards. The most portable
    choice you can make is to always use 'va_list *' (rather
    than va_list) for any function parameter. It works
    correctly across implementations, in every version of
    standard C.
     
    Tim Rentsch, Jan 18, 2013
    #33
  14. The 6600 had 18-bit addresses. (Size of A & B registers)
    A char pointer had the word address in the lower 18 bits and an n-bit
    field elsewhere to identify which byte in a word.
    (To be accessed using mask and shift operations.)
     
    Roberto Waltman, Jan 20, 2013
    #34
    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.