将此作为参数使用 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.

所以即使这对你有用,也不能保证一定有用,你不应该依赖它。