将va_start宏与此参数一起使用是否安全?

时间:2019-04-17 13:31:10

标签: c++ language-lawyer c++03 variadic

我必须在嵌入式应用程序中使用IAR编译器(它没有名称空间,异常,多重/虚拟继承,模板受位限制并且仅支持C ++ 03)。 我无法使用参数包,因此尝试使用可变参数创建成员函数。 我知道可变参数通常是不安全的。但是在this宏中使用va_start指针是否安全?

如果我使用普通的可变参数函数,则在...之前需要一个伪参数才能访问其余参数。我知道可变参数宏在...之前不需要参数,但我不希望使用它。 如果我使用成员函数,它在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一样崩溃。我不介意。

4 个答案:

答案 0 :(得分:20)

什么都没有在标准中指定该行为,因此此构造仅调用正式的Undefined Behaviour。这意味着它可以在您的实现中正常运行,并在其他实现中导致编译错误或意外结果。

非静态方法必须传递隐藏的this指针这一事实不能保证va_start可以使用它。之所以可能如此,是因为在早期,C ++编译器只是将C ++源代码转换为C源代码的预处理器,并且隐藏的this参数是由预处理器添加的,可供C编译器使用。而且可能是出于 compatibility 原因进行维护的。但是我会尽力避免在关键任务代码中出现这种情况...

答案 1 :(得分:3)

似乎是未定义的行为。如果看一下va_start(ap, pN)在许多实现中的作用(请检查头文件),它将获取pN的地址,将指针增加pN的大小,并将结果存储在ap中。我们可以合法地查看&this吗?

我在这里找到了很好的参考文献:https://stackoverflow.com/a/9115110/10316011

  

引用2003 C ++标准:

     

5.1 [expr.prim]关键字,它命名一个指向对象的指针,该对象将为其调用非静态成员函数(9.3.2)。 ...表达式的类型是指向函数类(9.3.2)的指针,...表达式是一个右值。

     

5.3.1 [expr.unary.op]一元&运算符的结果是指向其操作数的指针。操作数应为左值或qualified_id。

因此,即使这对您有用,也不能保证,您也不应该依赖它。

答案 2 :(得分:2)

我认为应该没问题,尽管我怀疑您会从C ++标准中找到特定的引用。

基本原理是:va_start()必须传递给函数的最后一个参数。没有显式参数的成员函数只有一个参数(this),因此必须是它的最后一个参数。

如果您曾经在无法运行的平台上进行编译(这似乎不太可能,但是您又已经在某种非典型的平台上进行编译),那么添加单元测试就可以提醒您。 >

答案 3 :(得分:0)

这是未定义的行为。由于该语言不需要将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。这意味着此优化问题不适用于此处。