Search code examples
cgcccygwin

va_arg gives something strange in cygwin x64


asn1c compiler has the following code (it's C code, not C++):

return asn1c_make_identifier(
                        AMI_MASK_ONLY_SPACES | AMI_NODELIMITER,
                        0, ((!stdname || (arg->flags & A1C_INCLUDES_QUOTED))
                                ? "\"" : "<"),
                        exprid ? exprid->Identifier : typename,
                        ((!stdname || (arg->flags & A1C_INCLUDES_QUOTED))
                                ? ".h\"" : ".h>"), 0);

For simplicity I could write the following (it doesn't affect the bug):

return asn1c_make_identifier(
                        AMI_MASK_ONLY_SPACES | AMI_NODELIMITER,
                        0, "str1",
                        "str2",
                        "str3", 0);

The function asn1c_make_identifier has the following definition char * asn1c_make_identifier(enum ami_flags_e flags, asn1p_expr_t *expr, ...) and standart va_list processing loop:

va_start(ap, expr);
while((str = va_arg(ap, char *)))
    size += 1 + strlen(str);
va_end(ap);

So, we get 3 strings and one zero. Everything seems to be good (and it works in Linux), but in cygwin I get the fourth iteration with non zero str. As a result segmentation fault happens. But if I write (char*) before 0 the code works as it should. Is it a real bug of cygwin or is it a bug in the source code? It's not my code and it contains a number of similiar zeros.

P.S. About gcc version

gcc --version
gcc (GCC) 4.8.3

Solution

  • The problem is that 0 is being interpreted as an integer, not as a pointer. Since the compiler cannot know what the types of variadic arguments are supposed to be, it can't convert to the type you intend.

    So, when the compiler sees the expression 0 as an argument, it interprets it as an expression of type int, not of type void * (or char *, or any other pointer).

    Because you're compiling for x64, sizeof(int) is 4 and sizeof(char *) is 8, so va_arg is reading more data from the stack than what's passed to it.

    Instead, you should pass the constant NULL, which is of type void *, or for maximum portability, pass the expression (char *)NULL.