我有这段代码(总结)...
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
的工作方式造成的,但我不太确定发生了什么。
如果你看看 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");
这是 C++ 标准(18.7 - 其他运行时支持)关于
va_start()
的说法(强调我的):
ISO C 的限制 第二个参数为 标题中的
宏va_start()
在这方面有所不同 国际标准。参数<stdarg.h>
是标识符 变量中最右边的参数 函数的参数列表 定义(紧接在parmN
)。 如果参数...
是用函数、数组或引用声明的 类型, 或类型不是 与结果类型兼容 当传递一个参数时 没有参数,行为 未定义。parmN
正如其他人提到的,如果将可变参数与非直接 C 项(甚至可能以其他方式)一起使用,则在 C++ 中使用可变参数是危险的。
也就是说 - 我仍然一直使用 printf()...
根据 C++ 编码标准(Sutter、Alexandrescu):
varargs 永远不应该与 C++ 一起使用:
它们不是类型安全的,并且对于类类型的对象具有未定义的行为,这可能会导致您的问题。
这是我的简单解决方法(使用 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);
}
旁注:
类类型作为可变参数参数的行为可能是未定义的,但根据我的经验,这是一致的。编译器将类内存的 sizeof(class) 压入堆栈。即,用伪代码:
alloca(sizeof(class));
memcpy(stack, &instance, sizeof(class);
对于以非常有创意的方式利用这一点的一个非常有趣的示例,请注意,您可以将 CString 实例代替 LPCTSTR 直接传递给可变参数函数,并且它可以工作,并且不涉及转换。我把它作为练习留给读者,让他们弄清楚他们是如何做到这一点的。