是否有使用带有参考参数的变量的陷阱
Are there gotchas using varargs with reference parameters
我有这段代码(总结)...
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的工作方式,但我不确定发生了什么。
如果你看看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++中使用varargs是危险的。
也就是说 - 我仍然一直使用 printf() ...
N0695 中找到了一个很好的分析,为什么你不希望这样做
根据C++编码标准(萨特,亚历山德雷斯库):
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);
}
旁注:
类类型作为 varargs 参数的行为可能未定义,但根据我的经验,它是一致的。编译器将类内存的大小(类)推送到堆栈上。即,在伪代码中:
alloca(sizeof(class));
memcpy(stack, &instance, sizeof(class);
对于以非常有创意的方式使用它的一个非常有趣的示例,请注意,您可以将CString实例代替LPCTSTR直接传递给varargs函数,并且它可以工作,并且不涉及强制转换。我把它留给读者练习,让他们弄清楚他们是如何做到这一点的。
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 将成员变量添加到共享库中的类中,不会破坏二进制兼容性吗
- 将数组的地址分配给变量并删除
- 为"adjacent"变量赋值时出现问题
- enum是C++中的宏变量还是整数变量
- 在全局变量中保存类的实例以重新创建类(创建"backup")
- 用C++中的一个变量定义一个常量
- 具有奇怪重复模板模式的派生类中的成员变量已损坏
- 你能重载对象变量名本身返回的内容吗
- 内置函数可查看CPP中的成员变量
- 是否可以初始化不可复制类型的成员变量(或基类)
- 尝试通过多个向量访问变量时,向量下标超出范围
- 试图让变量检查数组中的某些内容
- Cpp-Tuple使用带有变量的get
- 将包含C样式数组的对象初始化为成员变量(C++)
- 当vector是tje全局变量时,c++中vector的内存管理
- 通过多个头文件使用常量变量
- 如何检查 int 变量是否包含合法(非陷阱表示)值?
- C++11.Lambda 成员变量捕获,"this" STL 列表中的指针陷阱
- 是否有使用带有参考参数的变量的陷阱