使用va_start宏作为参数是否安全?

问题描述 投票:18回答:4

我必须在嵌入式应用程序中使用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那样崩溃。我不介意。

c++ language-lawyer c++03 variadic
4个回答
20
投票

Nothing没有指定标准中的行为,因此这个构造只调用正式的Undefined Behavior。这意味着它可以在您的实现中正常工作,并在不同的实现中导致编译错误或意外结果。

非静态方法必须通过隐藏的this指针的事实不能保证va_start可以使用它。它可能是这样工作的,因为在早期,C ++编译器只是将C ++源代码转换为C源代码的预处理器,而预处理器添加了隐藏的this参数,以供C编译器使用。并且出于兼容性原因,可能会对其进行维护。但我会尽力避免在任务关键代码中...


3
投票

似乎是未定义的行为。如果你看看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。

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


2
投票

我认为应该没问题,但我怀疑你会从C ++标准中找到一个特定的引用。

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

如果你在一个不起作用的平台上进行编译(这似乎不太可能,但是你再次在一个有点非典型的平台上编译),那么添加单元测试会很容易提醒你。


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。这意味着此优化问题不适用于此处。

© www.soinside.com 2019 - 2024. All rights reserved.