我遇到的问题非常类似于Linux内核社区所描述的问题 - Betrayed by a Bit-Field
问题的实质是GCC发出64位读访问,甚至访问1位位域。这会导致在相邻字段中读取的意外副作用,可以在程序中的其他位置进行修改。当写回修改的位域值时,相邻变量的旧值也会被写回,从而失去其他线程对其进行的任何修改。
我的问题略有不同。我有一个像这样的类/结构 -
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位读取并且它们是易失性的,如果它作为此读取的一部分向adjVariable
发出读取并且adjVariable
的显式ZERO分配仍在缓存中,该怎么办?因此我们读取adjVariable
的旧值12?这个新读取的值会覆盖显式设置值吗?因此我们正在绊倒assert
?如果是这样,我该如何验证?
在文章中,他们正在讨论丢失对其他线程中完成的相邻变量的更新,但在我的问题中,我怀疑我们在相同中完成的adjVariable
更新失败了>线程因为从内存中读取。这可能吗?
我们正在使用一个古老的g ++编译器,它在同样较旧的Fedora 12版虚拟机上只兼容C ++ 98。此外,我们只遇到一个运行了6个月的代码库
,就遇到了这个问题答案 0 :(得分:1)
如果没有从任何其他并发线程访问adjVariable
,那么它几乎可以保证在断言点为0。
虽然所有bool
都是单个内存位置,并且确实可以在它们之间产生一些奇怪的行为,adjVariable
是一个单独的内存位置,编译器必须确保所有的加载和存储它似乎以符合源代码的明确定义的顺序发生。
如果编译器为位字段发出64位写入,那么它必须通过将位字段对齐到8个字节来保护相邻的存储器位置(例如,{{1之间应该有7字节填充) }和adjVariable
)。我不知道64位读取如何在这里弄错正确性。
虽然内存位置的概念仅适用于C ++ 11及更高版本,但逻辑仍适用于C ++ 98:flag1
不能为零的唯一方法断言应该是另一个线程写入adjVariable
。