我面临的问题与Linux内核社区所描述的问题非常相似-Betrayed by a Bit-Field

问题的实质在于,GCC发出64位读取访问以访问偶数1位位域。这会导致读取相邻字段的意外副作用,该副作用可在程序的其他位置进行修改。当修改后的位域值被写回时,相邻变量的旧值也被写回,从而丢失了其他线程对其所做的任何修改。

我的问题略有不同。我有一个这样的类(class)/结构-

class Group {

    uint8 adjVariable;
    volatile bool  flag1: 1;
    volatile bool  flag2: 1;
    // so on...
    volatile bool  flag10: 1;
};

访问这些变量的方式是-
Group::fun() {
    Group_Scoped_lock();
    // adjVariable was 12 here.
    if ( adjVariable > 0 ) {
        adjVariable = 0; // <------- EXPLICIT ZERO ASSIGNMENT
    }
    // some code that doesn't affect adjVariable
    bool1 = false;
    bool2 = false;
    bool3 = false;
    assert( adjVariable == 0 ); // <---- This assert is tripping stating that adjVariable is 12!!
}

在我们怀疑GCC出现“错误”之前,我验证了是否在没有adjVariable的情况下访问了Group_lock()。尽我所能,我看不到代码中发生这种情况的任何地方。

现在,由于编译器对位域发出64位读取并且它们是 volatile 的,如果它作为读取的一部分向adjVariable发出读取并且adjVariable的显式ZERO分配仍在高速缓存中,该怎么办,因此我们为adjVariable读取了较旧的值12 ?并且这个新读取的值会覆盖显式设置的值吗?因此,我们将assert绊倒了吗?如果是这样,我该如何验证呢?

在本文中,他们正在讨论丢失对其他线程中完成的相邻变量的更新,但是在我的问题中,我怀疑由于内存中的读取,我们正在丢失对同一线程中完成的adjVariable的更新。这可能吗?

我们正在使用古老的g++编译器,该编译器在同样旧的Fedora版本12虚拟机上仅符合C++ 98。另外,只有运行6个月的代码库才遇到此问题

最佳答案

如果没有从任何其他并发线程访问adjVariable,那么在断言的位置几乎可以保证它为0。

尽管所有bool都是一个单独的内存位置,并且确实会在它们之间产生一些奇怪的行为,但是adjVariable是一个单独的内存位置,编译器必须确保所有加载和存储看上去都按照定义明确的顺序进行到源代码。

如果编译器发出64位来为位字段写入,则它必须通过将位字段对齐为8个字节来保护相邻的内存位置(例如adjVariableflag1之间应该有7个字节的填充)。我看不出64位如何读取会在这里与正确性混淆。

尽管内存位置的概念仅适用于C++ 11和更高版本,但该逻辑仍适用于C++ 98:断言中adjVariable不为零的唯一方法应该是让另一个线程写入adjVariable

关于c++ - 位域附近的变量会损坏吗?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44993317/

10-09 07:13