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

发布于 2024-10-15 07:08:31 字数 547 浏览 8 评论 0原文

我有两个函数:

void prepare() 和 void finish() ,它们将按顺序调用,如下所示:

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

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

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

像这样的简单的assert()效果最好吗?

int test = 0;

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

   .
   .
   .
}

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

    .
    .
    .
}

I have two functions:

void prepare() and void finish() that will be called sequentially like:

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

I want to make a simple assertion to simply test that they are in fact being called this way and that they aren't being called concurrently or out-of-order in the application.

This application is a single-threaded application. This is a simple development/testing sanity check to make sure that these functions are being called in-order and that for whatever reason, they aren't being called concurrently. Furthermore, these assertions/sanity checks should be omitted from production code as performance is crucial!

would a simple assert() like this work best?

int test = 0;

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

   .
   .
   .
}

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

    .
    .
    .
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

韵柒 2024-10-22 07:08:31

您可能希望更改

int test = 0;

#ifndef NDEBUG
int test = 0;
#endif

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

You might want to change

int test = 0;

to

#ifndef NDEBUG
int test = 0;
#endif

to satisfy your requirement that "any code relating to this test should be omitted from production".

时光倒影 2024-10-22 07:08:31

你可能想要:

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
}

you probably want:

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
}
蓝色星空 2024-10-22 07:08:31

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

使其易失

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);
}

There's a race condition here: two concurrent instances of prepare might take the value of test at the same time, then both increment it in a register to both obtain 1, then do the comparison to get true.

Making it volatile is not going to help. Instead, you should put a mutex on test, like so:

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);
}
π浅易 2024-10-22 07:08:31

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

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

bool locked = false;;

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

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

Your code is OK, unless you need to allow nesting prepare and finish calls.

If nesting is not allowed, you could use a bool instead of an int:

bool locked = false;;

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

void finish() {
    assert( locked );
    locked = false;
    ...
}
北方。的韩爷 2024-10-22 07:08:31

如果将 >; 放入 class 中,则可以完全减少检查的需要:

只需让构造函数调用 prepare析构函数调用finish。然后它会自动强制正确调用它们。

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

另请注意,您还可以将运算符设为私有 new/delete ,以防止有人在堆上创建一个运算符而不销毁它。

If you put <do something>; into a class you can reduce the need for a check at all:

Just have the constructor call prepare and the destructor call finish. Then it's automatically enforced that they're called appropriately.

Note that concurrency and nesting issues still apply: If you want to prevent nesting then you'd still need some sort of global state (static class member?) to keep track of that, and if it's used in more than one thread access to that counter would need to be mutex protected.

Also note that you could also make private operator new/delete to prevent someone from creating one on the heap and not destroying it.

绝對不後悔。 2024-10-22 07:08:31

既然您使用的是 C++,为什么不使用 RAII?您仍然需要检查可重入使用,但 RAII 大大简化了事情。与 NDEBUG 中的 larsmans' mutexRaedwald 消除相结合

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 的旁注:如果您以这种方式使用它,请确保它在所有翻译单元中始终定义或始终未定义,而不是它用于断言的方式(允许定义和未定义)在不同的点)。

Since you're using C++, why not use RAII? You'd still need to check for re-entrant use, but RAII simplifies things considerably. Combined with larsmans' mutex and Raedwald'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()
}

Inside example, you simply cannot do something without first preparing, and finishing will always happen, even if an exception is thrown.

Side note about NDEBUG: if you use it this way, make sure it is either always defined or always undefined in all translation units, as opposed to how it's used for assert (allowing it to be defined and undefined at various points).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文