我必须在嵌入式应用程序中使用IAR compiller(它没有命名空间,异常,多个/虚拟继承,模板有点受限,只支持C ++ 03)。我不能使用参数包,所以我尝试使用variadic参数创建成员函数。我知道可变参数通常是不安全的。但是在this
宏中使用va_start
指针是否安全?
如果我使用普通的可变参数函数,那么在...
之前需要一个伪参数才能访问剩余的参数。我知道variadic宏在...
之前不需要参数,但我不想使用它。如果我使用成员函数,它在this
之前隐藏了...
参数,所以我尝试了它:
struct VariadicTestBase{
virtual void DO(...)=0;
};
struct VariadicTest: public VariadicTestBase{
virtual void DO(...){
va_list args;
va_start(args, this);
vprintf("%d%d%d\n",args);
va_end(args);
}
};
//Now I can do
VariadicTestBase *tst = new VariadicTest;
tst->DO(1,2,3);
tst->DO(1,2,3);
按预期打印123。但我不确定这不仅仅是一些随机/未定义的行为。我知道tst->DO(1,2);
会像普通的prinf那样崩溃。我不介意。
Nothing没有指定标准中的行为,因此这个构造只调用正式的Undefined Behavior。这意味着它可以在您的实现中正常工作,并在不同的实现中导致编译错误或意外结果。
非静态方法必须通过隐藏的this
指针的事实不能保证va_start
可以使用它。它可能是这样工作的,因为在早期,C ++编译器只是将C ++源代码转换为C源代码的预处理器,而预处理器添加了隐藏的this
参数,以供C编译器使用。并且出于兼容性原因,可能会对其进行维护。但我会尽力避免在任务关键代码中...
似乎是未定义的行为。如果你看看va_start(ap, pN)
在许多实现中做了什么(检查你的头文件),它需要pN的地址,将指针增加pN的大小并将结果存储在ap中。我们可以合法地看看&this
吗?
我在这里找到了一个很好的参考:https://stackoverflow.com/a/9115110/10316011
引用2003 C ++标准:
5.1 [expr.prim]关键字this指定一个指向调用了非静态成员函数(9.3.2)的对象的指针。 ...表达式的类型是指向函数类(9.3.2)的指针,...表达式是一个右值。
5.3.1 [expr.unary.op]一元&运算符的结果是指向其操作数的指针。操作数应为左值或qualified_id。
因此,即使这对您有效,也不能保证,您不应该依赖它。
我认为应该没问题,但我怀疑你会从C ++标准中找到一个特定的引用。
基本原理是:va_start()
必须传递给函数的最后一个参数。不带显式参数的成员函数只有一个参数(this
),因此必须是它的最后一个参数。
如果你在一个不起作用的平台上进行编译(这似乎不太可能,但是你再次在一个有点非典型的平台上编译),那么添加单元测试会很容易提醒你。
这是未定义的行为。由于该语言不需要将this
作为参数传递,因此可能根本不会传递。
例如,如果编译器可以确定对象是单例,则可以避免将this
作为参数传递,并在明确要求this
的地址时使用全局符号(如va_start的情况)。从理论上讲,编译器可能会生成代码来补偿va_start
中的代码(毕竟,编译器知道这是一个单例),但标准并不要求这样做。
想想像:
class single {
public:
single(const single& )= delete;
single &operator=(const single& )= delete;
static single & get() {
// this is the only place that can construct the object.
// this address is know not later than load time:
static single x;
return x;
}
void print(...) {
va_list args;
va_start (args, this);
vprintf ("%d\n", args);
va_end (args);
}
private:
single() = default;
};
一些编译器,如clang 8.0.0,会针对上面的代码发出警告:
prog.cc:15:23: warning: second argument to 'va_start' is not the last named parameter [-Wvarargs] va_start (args, this);
尽管有警告,it runs ok。一般情况下,这没有任何证据,但发出警告是一个坏主意。
注意:我不知道有任何编译器检测单例并专门处理它们,但该语言并不禁止这种优化。如果您的编译器今天没有完成,明天可能会由另一个编译器完成。
注2:尽管如此,它可能在实践中将其传递给va_start
。即使它有效,但做一些不受标准保证的事情并不是一个好主意。
注3:相同的单例优化不能应用于以下参数:
void foo(singleton * x, ...)
它不能被优化掉,因为它可能有两个值中的一个,指向单身或者是nullptr
。这意味着此优化问题不适用于此处。