使用带参考参数的可变参数是否存在问题

问题描述 投票:0回答:6

我有这段代码(总结)...

AnsiString working(AnsiString format,...)
{
    va_list argptr;
    AnsiString buff;

    va_start(argptr, format);
    buff.vprintf(format.c_str(), argptr);

    va_end(argptr);
    return buff;
}

而且,在可能的情况下首选通过引用传递的基础上,我因此进行了更改。

AnsiString broken(const AnsiString &format,...)
{
    /* ... the rest, totally identical ... */
}

我的调用代码是这样的:

AnsiString s1 = working("Hello %s", "World"); // prints "Hello World"
AnsiString s2 = broken("Hello %s", "World");  // prints "Hello (null)"

我认为这是由于

va_start
的工作方式造成的,但我不太确定发生了什么。

c++ reference variadic-functions
6个回答
43
投票

如果你看看 va_start 扩展成什么,你就会看到发生了什么:

va_start(argptr, format); 

变成(大致)

argptr = (va_list) (&format+1);

如果 format 是值类型,它将被放置在堆栈中所有可变参数之前。如果 format 是引用类型,则仅将地址放入堆栈中。当您获取引用变量的地址时,您将获得地址或原始变量(在本例中是在调用 Broken 之前创建的临时 AnsiString),而不是参数的地址。

如果您不想传递完整的类,您的选择是通过指针传递,或放入虚拟参数:

AnsiString working_ptr(const AnsiString *format,...)
{
    ASSERT(format != NULL);
    va_list argptr;
    AnsiString buff;

    va_start(argptr, format);
    buff.vprintf(format->c_str(), argptr);

    va_end(argptr);
    return buff;
}

...

AnsiString format = "Hello %s";
s1 = working_ptr(&format, "World");

AnsiString working_dummy(const AnsiString &format, int dummy, ...)
{
    va_list argptr;
    AnsiString buff;

    va_start(argptr, dummy);
    buff.vprintf(format.c_str(), argptr);

    va_end(argptr);
    return buff;
}

...

s1 = working_dummy("Hello %s", 0, "World");

15
投票

这是 C++ 标准(18.7 - 其他运行时支持)关于

va_start()
的说法(强调我的):

ISO C 的限制 第二个参数为 标题中的

va_start()
<stdarg.h>
在这方面有所不同 国际标准。参数
parmN
是标识符 变量中最右边的参数 函数的参数列表 定义(紧接在
...
)。 如果参数
parmN
是用函数、数组或引用声明的 类型,
或类型不是 与结果类型兼容 当传递一个参数时 没有参数,行为 未定义

正如其他人提到的,如果将可变参数与非直接 C 项(甚至可能以其他方式)一起使用,则在 C++ 中使用可变参数是危险的。

也就是说 - 我仍然一直使用 printf()...


4
投票

N0695

中找到了为什么你不想要这个的很好的分析

2
投票

根据 C++ 编码标准(Sutter、Alexandrescu):

varargs 永远不应该与 C++ 一起使用:

它们不是类型安全的,并且对于类类型的对象具有未定义的行为,这可能会导致您的问题。


1
投票

这是我的简单解决方法(使用 Visual C++ 2010 编译):

void not_broken(const string& format,...)
{
  va_list argptr;
  _asm {
    lea eax, [format];
    add eax, 4;
    mov [argptr], eax;
  }

  vprintf(format.c_str(), argptr);
}

-1
投票

旁注:

类类型作为可变参数参数的行为可能是未定义的,但根据我的经验,这是一致的。编译器将类内存的 sizeof(class) 压入堆栈。即,用伪代码:

alloca(sizeof(class));
memcpy(stack, &instance, sizeof(class);

对于以非常有创意的方式利用这一点的一个非常有趣的示例,请注意,您可以将 CString 实例代替 LPCTSTR 直接传递给可变参数函数,并且它可以工作,并且不涉及转换。我把它作为练习留给读者,让他们弄清楚他们是如何做到这一点的。

© www.soinside.com 2019 - 2024. All rights reserved.