本文介绍了Linux 上的 va_list 不当行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一些代码可以将可变参数转换为 va_list,然后将列表传递给一个函数,然后该函数调用 vsnprintf.这在 Windows 和 OS X 上运行良好,但在 Linux 上运行失败,结果很奇怪.

I have some code that converts variadic parameters into a va_list, then passes the list on to a function that then calls vsnprintf. This works fine on Windows and OS X, but it is failing with odd results on 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;
}

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

The output of second vsnprintf call is 17, and the result of strcmp is 31; but I don't get why vsnprintf would return 17 seeing as This is a test. is 15 characters, add the NULL and you get 16.

我看过但未涉及该主题的相关主题:

Related threads that I've seen but do not address the topic:

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

With @Mat's answer (I am reusing the va_list object, which is not allowed), this comes squarely around to the first related thread I linked to. So I attempted this code instead:

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;
}

其中,根据 C99 规范(第 7.15 节中的脚注),应该可以:

Which, per the C99 spec (footnote in Section 7.15), should work:

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

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

But my compiler (gcc 4.4.5 in C99 mode) gives me this error regarding the first line of myPrintfInner:

test.c: In function ‘myPrintfInner’:
test.c:8: warning: initialization from incompatible pointer type

生成的二进制文件产生的效果与第一次完全相同.

And the resulting binary produces the exact same effect as the first time around.

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

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

The suggested workaround (which wasn't guaranteed to work, but did in practice) is to use arg_copy first:

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;
}

推荐答案

正如 Mat 所指出的,问题在于您正在重用 va_list.如果你不想按照他的建议重构你的代码,你可以使用 C99 va_copy() 宏,像这样:

As Mat notes, the problem is that you're reusing the va_list. If you don't want to restructure your code as he suggests, you can use the C99 va_copy() macro, like this:

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;
}

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

On compilers that don't support C99, you may be able use __va_copy() instead or define your own va_copy() implementation (which will be non-portable, but you can always use compiler / platform sniffing in a header file if you really need to). But really, it's been 13 years — any decent compiler should support C99 these days, at least if you give it the right options (-std=c99 for GCC).

这篇关于Linux 上的 va_list 不当行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-20 23:55