对有序非重入调用的简单断言?

时间:2011-02-01 15:45:32

标签: c++ assert correctness

我有两个功能:

void prepare()和void finish()将按顺序调用,如:

prepare();
<do something>;
finish(); 
... 
prepare(); 
<do something>; 
finish();

我想做一个简单的断言来简单地测试它们实际上是以这种方式调用的,并且它们不是在应用程序中同时或无序调用。

此应用程序是单线程应用程序。这是一个简单的开发/测试健全性检查,以确保按顺序调用这些函数,并且无论出于何种原因,它们都不会被同时调用。此外,这些断言/健全性检查应该从生产代码中省略,因为性能至关重要!

这样一个简单的断言()最好吗?

int test = 0;

void prepare() {
   assert(++test == 1);

   .
   .
   .
}

void finish() {
    assert(--test == 0);

    .
    .
    .
}

6 个答案:

答案 0 :(得分:3)

这里有竞争条件:prepare的两个并发实例可能同时取test的值,然后在寄存器中将它递增以获得1,然后进行比较以获得true

volatile设为not going to help。相反,你应该在test上加一个互斥量,如下所示:

boost::mutex mtx;
int test = 0;

void prepare()
{
    boost::mutex::scoped_try_lock lock(&mtx);
    assert(lock.owns_lock());
    assert(test++ == 0);
    // ...
}

void finish()
{
    boost::mutex::scoped_try_lock lock(&mtx);
    assert(lock.owns_lock());
    assert(--test == 0);
}

答案 1 :(得分:3)

您可能想要更改

int test = 0;

#ifndef NDEBUG
int test = 0;
#endif

满足您的要求“任何与此测试相关的代码都应从生产中省略”。

答案 2 :(得分:3)

你可能想要:

int test = 0;

void prepare() {
    // enter critical section
    assert(test++ == 0);

    .
    .
    .
    // leave critical section 
}

void finish() {
    // enter critical section
    assert(--test == 0);

    .
    .
    .
    // leave critical section
}

答案 3 :(得分:1)

您的代码没问题,除非您需要允许嵌套preparefinish来电。

如果不允许嵌套,您可以使用bool代替int

bool locked = false;;

void prepare() {
    assert( ! locked );
    locked = true;
    ...
}

void finish() {
    assert( locked );
    locked = false;
    ...
}

答案 4 :(得分:1)

由于您使用的是C ++,为什么不使用RAII?你仍然需要检查重入使用,但RAII大大简化了事情。结合larsmans' mutexRaedwald's elimination in NDEBUG

struct Frobber {
  Frobber() {
    assert(mtx.try_lock());
#ifndef NDEBUG
    try {  // in case prepare throws
#endif
      prepare();
#ifndef NDEBUG
    }
    catch (...) {
      mtx.unlock();
      throw;
    }
#endif
  }

  void something();
  // And the other actions that can be performed between preparation and finishing.

  ~Frobber() {
    finish();
#ifndef NDEBUG
    mtx.unlock();
#endif
  }

private:
#ifndef NDEBUG
  static boost::mutex mtx;
#endif

  Frobber(Frobber const&);  // not defined; 0x: = delete
  Frobber& operator=(Frobber const&);  // not defined; 0x: = delete
};
#ifndef NDEBUG
boost::mutex Frobber::mtx;
#endif

void example() {
  Frobber blah;  // instead of prepare()
  blah.something();
  // implicit finish()
}

在示例中,您只是无法在没有事先准备的情况下执行某些操作,即使抛出异常,也会始终完成。

关于NDEBUG的附注:如果你这样使用它,请确保它总是在所有转换单元中定义或总是未定义,而不是它用于断言的方式(允许它是在各个点定义和未定义。)

答案 5 :(得分:1)

如果您将<do something>;放入class,则可以减少检查的需要:

只需让构造函数调用prepare,然后调用析构函数finish。然后自动强制执行它们被恰当地调用。

请注意,并发和嵌套问题仍然适用:如果要防止嵌套,那么您仍然需要某种全局状态(静态类成员?)来跟踪它,以及它是否在多个线程中使用访问该计数器需要受互斥保护。

另请注意,您还可以设置私有operator new/delete以阻止某人在堆上创建一个而不会销毁它。