我正在为一个类编写单元测试,以在没有可用内存时测试插入。它依赖于 nbElementInserted
在 insert_edge
返回后递增这一事实。
void test()
{
adjacency_list a(true);
MemoryVacuum no_memory_after_this_line;
bool signalReceived = false;
size_t nbElementInserted = 0;
do
{
try
{
a.insert_edge( 0, 1, true ); // this should throw
nbElementInserted++;
}
catch(std::bad_alloc &)
{
signalReceived = true;
}
}
while (!signalReceived); // this loop is necessary because the
// memory vacuum only prevents new memory
// pages from being mapped. so the first
// allocations may succeed.
CHECK_EQUAL( nbElementInserted, a.nb_edges() );
}
现在我想知道这两个陈述中哪一个是正确的:
- 可能会发生重新排序,在这种情况下,
nbElementInserted
可以在insert_edge
引发异常之前递增,这使我的情况无效。可能会发生重新排序,因为如果排列这两行,用户的可见结果是相同的。 - 不会发生重新排序,因为
insert_edge
是一个函数,该函数的所有副作用都应该在转到下一行之前完成。 throw 是一种副作用。
奖励点:如果正确答案是“是的,可能会发生重新排序”,那么两行之间的内存屏障是否足以修复它?
最佳答案
没有。重新排序仅在多线程或多处理场景中发挥作用。在单线程中,编译器无法以改变程序行为的方式重新排序指令。异常(exception)情况也不异常(exception)。
当两个线程读写共享状态时,重新排序变得可见。如果线程 A 对共享变量进行了修改,线程 B 可以乱序看到这些修改,如果它缓存了共享状态,甚至根本看不到。这可能是由于线程 A 或线程 B 或两者的优化所致。
不过,线程 A 总是会按顺序看到自己的修改。每个sequence point必须按顺序发生,至少就本地线程而言是这样。
假设线程 A 执行了这段代码:
a = foo() + bar();
b = baz;
每个;
引入一个序列点。允许编译器首先调用 foo()
或 bar()
,无论哪个,因为 +
不引入序列点。如果您放置打印输出,您可能会看到 foo()
首先被调用,或者您可能会看到 bar()
首先被调用。任何一个都是正确的。不过,它必须在将 baz
分配给 b
之前调用它们。如果 foo()
或 bar()
抛出异常 b
必须保留其现有值。
但是,如果编译器知道 foo()
和 bar()
永远不会抛出,并且它们的执行绝不会依赖于 b< 的值
,它可以重新排序这两个语句。这将是一个有效的优化。线程 A 无法知道语句已重新排序。
另一方面,线程 B 会知道。多线程编程中的问题是序列点不适用于其他线程。这就是内存屏障的用武之地。从某种意义上说,内存屏障是跨线程序列点。
关于c++ - 代码重新排序会影响我的测试吗,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17494872/