将此作为参数使用 va_start 宏是否安全?
Is safe to use va_start macro with this as parameter?
我必须在嵌入式应用程序中使用 IAR 编译器(它没有名称空间、异常、multiple/virtual 继承、模板位有限且仅支持 C++03)。
我不能使用参数包,所以我尝试使用可变参数创建成员函数。
我知道可变参数通常是不安全的。但是在 va_start
宏中使用 this
指针安全吗?
如果我使用普通的可变参数函数,则在 ...
之前需要一个虚拟参数才能访问其余参数。我知道可变参数宏在 ...
之前不需要参数,但我不想使用它。
如果我使用成员函数,它在 ...
之前隐藏了 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。但我不确定这是否不仅仅是一些 random/undefined 行为。我知道 tst->DO(1,2);
会像普通 prinf 一样崩溃。我不介意。
我认为它应该没问题,尽管我怀疑您会从 C++ 标准中找到这样的特定引文。
基本原理是:va_start()
必须将最后一个参数传递给函数。没有显式参数的成员函数只有一个参数 (this
),因此它必须是它的最后一个参数。
很容易添加一个单元测试来提醒你,如果你在一个它不起作用的平台上编译(这似乎不太可能,但你已经在一个有点不典型的平台上编译了)。
标准中没有指定该行为,因此该构造仅调用正式的未定义行为。这意味着它可以在您的实现中正常工作,并在不同的实现中导致编译错误或意外结果。
非静态方法必须传递隐藏的 this
指针这一事实不能保证 va_start
可以使用它。它可能是这样工作的,因为在早期,C++ 编译器只是将 C++ 源代码转换为 C 源代码的预处理器,预处理器添加了隐藏的 this
参数以供 C 编译器使用。并且可能出于 兼容性 原因对其进行了维护。但我会尽量避免在关键任务代码中出现这种情况...
这是未定义的行为。由于该语言不要求将 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
。这意味着此优化问题不适用于此处。
似乎是未定义的行为。如果您查看 va_start(ap, pN)
在许多实现中的作用(检查您的头文件),它获取 pN 的地址,将指针递增 pN 的大小并将结果存储在 ap 中。我们可以合法地查看 &this
吗?
我在这里找到了一个很好的参考:
Quoting the 2003 C++ standard:
5.1 [expr.prim] The keyword this names a pointer to the object for which a nonstatic member function (9.3.2) is invoked. ... The type of the expression is a pointer to the function’s class (9.3.2), ... The expression is an rvalue.
5.3.1 [expr.unary.op] The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified_id.
所以即使这对你有用,也不能保证一定有用,你不应该依赖它。
我必须在嵌入式应用程序中使用 IAR 编译器(它没有名称空间、异常、multiple/virtual 继承、模板位有限且仅支持 C++03)。
我不能使用参数包,所以我尝试使用可变参数创建成员函数。
我知道可变参数通常是不安全的。但是在 va_start
宏中使用 this
指针安全吗?
如果我使用普通的可变参数函数,则在 ...
之前需要一个虚拟参数才能访问其余参数。我知道可变参数宏在 ...
之前不需要参数,但我不想使用它。
如果我使用成员函数,它在 ...
之前隐藏了 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。但我不确定这是否不仅仅是一些 random/undefined 行为。我知道 tst->DO(1,2);
会像普通 prinf 一样崩溃。我不介意。
我认为它应该没问题,尽管我怀疑您会从 C++ 标准中找到这样的特定引文。
基本原理是:va_start()
必须将最后一个参数传递给函数。没有显式参数的成员函数只有一个参数 (this
),因此它必须是它的最后一个参数。
很容易添加一个单元测试来提醒你,如果你在一个它不起作用的平台上编译(这似乎不太可能,但你已经在一个有点不典型的平台上编译了)。
标准中没有指定该行为,因此该构造仅调用正式的未定义行为。这意味着它可以在您的实现中正常工作,并在不同的实现中导致编译错误或意外结果。
非静态方法必须传递隐藏的 this
指针这一事实不能保证 va_start
可以使用它。它可能是这样工作的,因为在早期,C++ 编译器只是将 C++ 源代码转换为 C 源代码的预处理器,预处理器添加了隐藏的 this
参数以供 C 编译器使用。并且可能出于 兼容性 原因对其进行了维护。但我会尽量避免在关键任务代码中出现这种情况...
这是未定义的行为。由于该语言不要求将 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
。这意味着此优化问题不适用于此处。
似乎是未定义的行为。如果您查看 va_start(ap, pN)
在许多实现中的作用(检查您的头文件),它获取 pN 的地址,将指针递增 pN 的大小并将结果存储在 ap 中。我们可以合法地查看 &this
吗?
我在这里找到了一个很好的参考:
Quoting the 2003 C++ standard:
5.1 [expr.prim] The keyword this names a pointer to the object for which a nonstatic member function (9.3.2) is invoked. ... The type of the expression is a pointer to the function’s class (9.3.2), ... The expression is an rvalue.
5.3.1 [expr.unary.op] The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified_id.
所以即使这对你有用,也不能保证一定有用,你不应该依赖它。