1

我正在为一个类编写一个单元测试,以在没有可用内存时测试插入。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它是一个函数,并且该函数的所有副作用都应该在转到下一行之前完成。投掷是副作用。

奖励点:如果正确答案是“是的,可能会发生重新排序”,那么 2 行之间的内存屏障是否足以修复它?

4

1 回答 1

2

不会。重新排序仅在多线程或多处理场景中发挥作用。在单个线程中,编译器无法以会改变程序行为的方式重新排序指令。例外不是此规则的例外。

当两个线程读取和写入共享状态时,重新排序变得可见。如果线程 A 对共享变量进行修改,线程 B 可以看到这些修改乱序,或者如果它缓存了共享状态,甚至根本看不到这些修改。这可能是由于线程 A 或线程 B 或两者中的优化。

不过,线程 A 将始终按顺序看到自己的修改。每个序列点必须按顺序发生,至少就本地线程所知。

假设线程 A 执行了以下代码:

a = foo() + bar();
b = baz;

每个都;引入了一个序列点。允许编译器调用其中一个foo()bar()首先调用,无论它喜欢哪个,因为+不引入序列点。如果您放置打印输出,您可能会看到foo()先调用,或者您可能会看到bar()先调用。任何一个都是正确的。但是,它必须在分配baz给之前调用它们b。如果其中一个foo()bar()抛出异常,则b 必须保留其现有值。

但是,如果编译器知道这一点foo()并且bar()从不抛出,并且它们的执行完全不依赖于 的值b,它可以重新排序这两个语句。这将是一个有效的优化。线程 A 无法知道语句已重新排序。

另一方面,线程 B 会知道。多线程编程的问题是序列点不适用于其他线程。这就是内存屏障的用武之地。从某种意义上说,内存屏障是跨线程序列点。

于 2013-07-05T18:27:09.320 回答