简体   繁体   中英

What is wrong with this recursive va_arg code?

I'm trying to make a generic function taking a variable argument list. A part of the design is that some of these functions call each other. Unfortunately it doesn't seem to work. As you can see if you run the simple code below, the call to command() always fails, but the direct call to marshal_size() succeeds in decoding the two strings "FIRST_STR_ARG" and "SECOND_STR_ARG" according to the format string "FORMAT_STRING".

What is wrong in my reasoning?

The sample code compiles equally well with "g++ main.cpp" or "gcc main.c".

Thanks,
jules

#include <stdarg.h>
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>

#define MARSHAL_FORMAT "%s%s"
#define FIRST_STR_ARG "THIS_IS_ARG_ONE"
#define SECOND_STR_ARG "THIS_IS_ARG_TWO"

#define d(msg__, ...) do { printf("%s@%d: "msg__"\n", __FILE__, __LINE__, ## __VA_ARGS__); } while (0)

static uint32_t
marshal_size(const char *format, ...)
{
    uint32_t retv = 0;
    uint8_t ub;
    uint16_t uw;
    uint32_t ul;
    char *s;
    va_list ap;

    if (!format || !strlen(format))
            return 0;
    d("format = %s \n", format);

    va_start(ap, format);
    for (; '\0' != *format; format++) {

            d("*format = %c \n", *format);
            if ('%' == *format) {
                    format++;
                    if ('u' == *format)
                            format++;
            } else {
                    d("FORMAT ERROR\n");
                    continue;
            }
            d("*format = %c \n", *format);

            switch (*format) {
            case 's':
                    s = va_arg(ap, char*);
                    d("va_arg = %s\n", (s ? s : "NULL"));
                    if (s)
                            retv += strlen(s) + 1;
                    break;
            case 'l':
                    ul = va_arg(ap, uint32_t);
                    retv += sizeof(uint32_t);
                    break;
            case 'w':
                    uw = (uint16_t)va_arg(ap, int);
                    retv += sizeof(uint16_t);
                    break;
            case 'b':
                    ub = (uint8_t)va_arg(ap, int);
                    retv += sizeof(uint8_t);
                    break;
            default:
                    goto exit;
            }

            continue;
    exit:
            break;
    }

    va_end(ap);

    return retv;
}

static uint32_t
command(const char * const format,
    ...)
{
    uint32_t retv;
    va_list ap;

    va_start(ap, format);

    retv = marshal_size(format, ap);

    va_end(ap);

    return retv;
}

int
main(int argc, char *argv)
{
    uint32_t size;

    size = command(MARSHAL_FORMAT, FIRST_STR_ARG, SECOND_STR_ARG);
    d("size = %d", size);

    size = marshal_size(MARSHAL_FORMAT, FIRST_STR_ARG, SECOND_STR_ARG);
    d("size = %d", size);

    return EXIT_SUCCESS;
}

You have to make marshal_size take a va_list rather than ... . See Question 15.12 in the FAQ at c-faq.com.

I don't think you can pass a va_list to a function that takes variable arguments. I believe you need another version of marshal_size that takes a va_list as a hard parameter.

I'm a little rusty on the details of how this all works underneath. So I won't attempt an explanation. But as support, I'd suggest this is why we have the vprintf, vfprintf, and vsprintf versions of such functions.

Most likely you could make marshal_size(const char *format, ...) call the marshal_size(const char *format, va_list arg_ptr) to actually perform its functionality, thus not having to duplicate any code. Then command could call the va_list version as well, and everything should work.

The return value of va_start (which is actually a macro) is a pointer to an element on the stack, so the address of something on the stack. If you pass this as argument to another function, you don't pass the stack value, but the pointer to the value on the stack.

To get this working, the command function should use va_arg to get the actual stack value and pass this to the marshal_size function, however this cannot be done if you don't know the type of the value on the stack. In that case, pass ap as va_list argument and change the argument of marshall_size to get a va_list argument and continue the stack value processing there.

As the other answers point out, if you want to "pass through" variable arguments from one function to another, you need to write your function to take an explicit va_list argument. This is quite easy: you just need to rewrite your marshal_size() function as vmarshal_size() that takes a va_list , then turn marshal_size() itself into a wrapper:

static uint32_t
marshal_size(const char *format, ...)
{
    va_list ap;
    uint32_t retval;

    va_start(ap, format);
    retval = vmarshal_size(format, ap);
    va_end(ap);

    return retval;
}

static uint32_t
vmarshal_size(const char *format, va_list ap)
{
    uint32_t retv = 0;
    uint8_t ub;
    uint16_t uw;
    uint32_t ul;
    char *s;

    if (!format || !strlen(format))
            return 0;
    d("format = %s \n", format);

    /* No va_start() */

    for (; '\0' != *format; format++) {

            d("*format = %c \n", *format);
            if ('%' == *format) {
                    format++;
                    if ('u' == *format)
                            format++;
            } else {
                    d("FORMAT ERROR\n");
                    continue;
            }
            d("*format = %c \n", *format);

            switch (*format) {
            case 's':
                    s = va_arg(ap, char*);
                    d("va_arg = %s\n", (s ? s : "NULL"));
                    if (s)
                            retv += strlen(s) + 1;
                    break;
            case 'l':
                    ul = va_arg(ap, uint32_t);
                    retv += sizeof(uint32_t);
                    break;
            case 'w':
                    uw = (uint16_t)va_arg(ap, int);
                    retv += sizeof(uint16_t);
                    break;
            case 'b':
                    ub = (uint8_t)va_arg(ap, int);
                    retv += sizeof(uint8_t);
                    break;
            default:
                    goto exit;
            }

            continue;
    exit:
            break;
    }

    /* No va_end() */

    return retv;
}

Your command() function can then call vmarshal_size() in the same way (although I note that your command() function is exactly the same as the new marshal_size() , presumably you actually want to do something different there).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM