Linux上的va_list错误行为

Mah*_*dsi 8 c variadic-functions

我有一些代码将variadic参数转换为a va_list,然后将列表传递给随后调用的函数vsnprintf.这在Windows和OS X上运行良好,但在Linux上却出现了奇怪的结果.

在以下代码示例中:

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

char *myPrintfInner(const char *message, va_list params)
{
    va_list *original = &params;
    size_t length = vsnprintf(NULL, 0, message, *original);
    char *final = (char *) malloc((length + 1) * sizeof(char));
    int result = vsnprintf(final, length + 1, message, params);

    printf("vsnprintf result: %d\r\n", result);
    printf("%s\r\n", final);

    return final;
}

char *myPrintf(const char *message, ...)
{
    va_list va_args;
    va_start(va_args, message);

    size_t length = vsnprintf(NULL, 0, message, va_args);
    char *final = (char *) malloc((length + 1) * sizeof(char));
    int result = vsnprintf(final, length + 1, message, va_args);

    printf("vsnprintf result: %d\r\n", result);
    printf("%s\r\n", final);

    va_end(va_args);

    return final;
}

int main(int argc, char **argv)
{
    char *test = myPrintf("This is a %s.", "test");
    char *actual = "This is a test.";
    int result = strcmp(test, actual);

    if (result != 0)
    {
        printf("%d: Test failure!\r\n", result);
    }
    else
    {
        printf("Test succeeded.\r\n");
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

第二次vsnprintf调用的输出为17,结果strcmp为31; 但我不知道为什么vsnprintf会返回17看到This is a test.15个字符,添加NULL,你得到16.

我见过但没有解决相关主题的相关主题:


使用@Mat的答案(我正在重用该va_list对象,这是不允许的),这恰好是我链接到的第一个相关线程.所以我尝试了这个代码:

char *myPrintfInner(const char *message, va_list params)
{
    va_list *original = &params;
    size_t length = vsnprintf(NULL, 0, message, params);
    char *final = (char *) malloc((length + 1) * sizeof(char));
    int result = vsnprintf(final, length + 1, message, *original);

    printf("vsnprintf result: %d\r\n", result);
    printf("%s\r\n", final);

    return final;
}
Run Code Online (Sandbox Code Playgroud)

其中,每C99规范(脚注第7.15),应该工作:

允许创建指向va_list的指针并将该指针传递给另一个函数,在这种情况下,原始函数可以在另一个函数返回后进一步使用原始列表.

但我的编译器(在C99模式下的gcc 4.4.5)给出了关于第一行的这个错误myPrintfInner:

test.c: In function ‘myPrintfInner’: 
test.c:8: warning: initialization from incompatible pointer type
Run Code Online (Sandbox Code Playgroud)

并且得到的二进制产生与第一次完全相同的效果.


发现这个:GCC是否错误处理了传递给函数的va_list的指针?

建议的解决方法(不保证可行,但在实践中确实)是首先使用arg_copy:

char *myPrintfInner(const char *message, va_list params)
{
    va_list args_copy;
    va_copy(args_copy, params);

    size_t length = vsnprintf(NULL, 0, message, params);
    char *final = (char *) malloc((length + 1) * sizeof(char));
    int result = vsnprintf(final, length + 1, message, args_copy);

    printf("vsnprintf result: %d\r\n", result);
    printf("%s\r\n", final);

    return final;
}
Run Code Online (Sandbox Code Playgroud)

Ilm*_*nen 12

Mat注意到,问题是你正在重复使用va_list.如果您不想按照他的建议重新构建代码,可以使用C99 va_copy()宏,如下所示:

char *myPrintfInner(const char *message, va_list params)
{
    va_list copy;

    va_copy(copy, params);
    size_t length = vsnprintf(NULL, 0, message, copy);
    va_end(copy);

    char *final = (char *) malloc((length + 1) * sizeof(char));
    int result = vsnprintf(final, length + 1, message, params);

    printf("vsnprintf result: %d\r\n", result);
    printf("%s\r\n", final);

    return final;
}
Run Code Online (Sandbox Code Playgroud)

在不支持C99的编译器上,您可以改为使用__va_copy()或定义自己的va_copy()实现(这将是不可移植的,但如果您确实需要,可以始终在头文件中使用编译器/平台嗅探).但实际上,这已经是13年了 - 现在任何体面的编译器都应该支持C99,至少如果你给它正确的选择(-std=c99对于GCC).


Mat*_*Mat 7

问题是(除了缺少的返回语句),您重新使用va_list参数而不重置它.这不好.

尝试类似的东西:

size_t myPrintfInnerLen(const char *message, va_list params)
{
    return vsnprintf(NULL, 0, message, params);
}

char *myPrintfInner(size_t length, const char *message, va_list params)
{
    char *final = (char *) malloc((length + 1) * sizeof(char));
    int result = vsnprintf(final, length + 1, message, params);

    printf("vsnprintf result: %d\r\n", result);
    printf("%s\r\n", final);

    return final;
}

char *myPrintf(const char *message, ...)
{
    va_list va_args;
    va_start(va_args, message);
    size_t length = myPrintfInnerLen(message, va_args);
    va_end(va_args);
    va_start(va_args, message);
    char *ret = myPrintfInner(length, message, va_args);
    va_end(va_args);
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

(并打开编译器的警告.)

我不认为你指出的脚注意味着你的想法.我把它读作:如果va_list直接传递(作为值,而不是指针),你可以在调用者中做的唯一事情是va_end它.但是如果你把它作为指针传递,你可以说,va_arg如果被调用者没有"消耗"所有的话,就会调用调用者va_list.

你可以尝试一下va_copy.就像是:

char *myPrintfInner(const char *message, va_list params)
{
    va_list temp;
    va_copy(temp, params);
    size_t length = vsnprintf(NULL, 0, message, temp);
    ...
Run Code Online (Sandbox Code Playgroud)