31

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

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, s2;
    s1 = working("Hello %s", "World");
    s2 = broken("Hello %s", "World");

但是,s1 包含“Hello World”,而 s2 包含“Hello (null)”。我认为这是由于 va_start 的工作方式,但我不确定发生了什么。

4

6 回答 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");
于 2008-10-21T15:25:29.640 回答
14

这是 C++ 标准(18.7 - 其他运行时支持)所说的va_start()(强调我的):

ISO C 对 va_start()标头中宏 的第二个参数的限制<stdarg.h>在本国际标准中有所不同。参数 parmN是函数定义的可变参数列表中最右边的参数的标识符(在 之前的那个 ...)。 如果parmN使用函数、数组或引用类型声明参数,或者使用与传递没有参数的参数时产生的类型不兼容的类型,则行为未定义

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

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

于 2008-10-21T15:34:32.617 回答
4

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

于 2008-10-21T15:07:58.380 回答
2

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

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

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

于 2008-10-21T15:10:00.217 回答
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);
}
于 2013-06-19T02:56:14.553 回答
0

边注:

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

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

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

于 2008-10-21T18:24:17.873 回答