Is GCC mishandling a pointer to a va_list passed to a function?

后端 未结 4 1399
日久生厌
日久生厌 2020-11-30 08:48

The question \'Pass va_list or pointer to va_list?\' has an answer which quotes the standard (ISO/IEC 9899:1999 - §7.15 \'Variable arguments , f

相关标签:
4条回答
  • 2020-11-30 09:08

    As others have noted, this issue manifests itself when va_list is an array type. This is allowed by the standard, which only says that va_list must be an "object type".

    You can fix the test_val() function like so:

    static void test_val(const char *fmt, va_list args)
    {
        va_list args_copy;
    
        /* Note: This seemingly unnecessary copy is required in case va_list
         * is an array type. */
        va_copy(args_copy, args);
        test_ptr(fmt, &args_copy);
        va_end(args_copy);
    }
    
    0 讨论(0)
  • 2020-11-30 09:09

    I think va_list must be declared as an array type, which “boils down” to a pointer type when declared as a parameter of a function. Therefore, the & applied to the va_list type in test_val yields a pointer to a pointer type, not a pointer to an array type, however, the test_ptr function declares one of its parameters as a pointer to array type, which is what is actually provided in the test function.

    0 讨论(0)
  • 2020-11-30 09:12

    This is a known problem. On some architectures (in particular x86-64), va_list needs to be more complex than a simple pointer to the stack, for example because some arguments might be passed in registers or out-of-band in some other way (see this answer for the definition of va_list on x86-64).

    On such architectures, it is common to make va_list an array type so that parameters of type va_list will be adjusted to pointer types, and instead of the whole structure, only a single pointer needs to be passed.

    This should not violate the C standard, which only says that va_list must be a complete object type and even explicitly accounts for the fact that passing a va_list argument might not actually clone the necessary state: va_list objects have indeterminate value if they are passed as arguments and consumed in the called function.

    But even if making va_list an array type is legal, it still leads to the problems you experienced: As parameters of type va_list have the 'wrong' type, eg struct __va_list_tag * instead of struct __va_list_tag [1], it will blow up in cases where the difference between arrays and pointers matter.

    The real problem is not the type mismatch gcc warns about, but the by-pointer instead of by-value argument passing semantics: &args in test_val() points to the intermediate pointer variable instead of the va_list object; ignoring the warning means that you'll invoke va_arg() in test_ptr() on the pointer variable, which should return garbage (or segfault if you're lucky) and corrupt the stack.

    One workaround is to wrap your va_list in a structure and pass that around instead. Another solution I've seen in the wild, even here on SO, is to use va_copy to create a local copy of the argument and then pass a pointer to that:

    static void test_val(const char *fmt, va_list args)
    {
        va_list args_copy;
        va_copy(args_copy, args);
        test_ptr(fmt, &args_copy);
        va_end(args_copy); 
    }
    

    This should work in practice, but technically it might or might not be undefined behaviour, depending on your interpretation of the standard:

    If va_copy() is implemented as a macro, no parameter adjustments are performed, and it might matter that args is not of type va_list. However, as it is unspecified whether va_copy() is a macro or a function, one might argue that it at least could be a function and parameter adjustments are implicitly assumed in the prototype given for the macro. It might be a good idea to ask the officials for clarification or even file a defect report.

    You could also use your build system to deal with the issue by defining a configuration flag like HAVE_VA_LIST_AS_ARRAY so you can do the right thing for your particular architecture:

    #ifdef HAVE_VA_LIST_AS_ARRAY
    #define MAKE_POINTER_FROM_VA_LIST_ARG(arg) ((va_list *)(arg))
    #else
    #define MAKE_POINTER_FROM_VA_LIST_ARG(arg) (&(arg))
    #endif
    
    static void test_val(const char *fmt, va_list args)
    {
        test_ptr(fmt, MAKE_POINTER_FROM_VA_LIST_ARG(args));
    }
    
    0 讨论(0)
  • 2020-11-30 09:14

    The problem is not specific to va_list. The following code results in a similar warning:

    typedef char *test[1];
    
    void x(test *a)
    {
    }
    
    void y(test o)
    {
        x(&o);
    }
    

    The problem stems from the way C handles function variables that are also arrays, probably due to the fact that arrays are passed as reference and not by value. The type of o is not the same as the type of a local variable of type test, in this case: char *** instead of char *(*)[1].

    Returning to the original issue at hand, the easy way to work around it is to use a container struct:

    struct va_list_wrapper {
        va_list v;
    };
    

    and there would be no typing problems passing a point to it.

    0 讨论(0)
提交回复
热议问题