Linux 上的 va_list 不当行为 [英] va_list misbehavior on Linux

查看:35
本文介绍了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 的指针并传递该指针到另一个函数,在这种情况下,原始函数可能会使在其他函数返回后进一步使用原始列表.

It is permitted to create a pointer to a va_list and pass that pointer to another function, in which case the original function may make further use of the original list after the other function returns.

但是我的编译器(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 不当行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆