我面临的问题非常类似于
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的“错误”之前,我验证了是否在没有Group_lock()的情况下访问adjVariable.尽我所能,我无法在代码中看到任何发生这种情况的地方.
现在,由于编译器为位域发出64位读取并且它们是易失性的,如果它作为读取的一部分向adjVariable发出读取并且adjVariable的显式ZERO赋值仍然在缓存中,那么我们为adjVariable读取旧值12 ?这个新读取的值会覆盖显式设置值吗?因此我们绊倒了这个断言?如果是这样,我该如何验证?
在文章中,他们正在讨论丢失对其他线程中完成的相邻变量的更新,但在我的问题中,我怀疑由于从内存中读取,我们在同一线程中对adjVariable的更新失败了.这可能吗?
我们正在使用一个古老的g编译器,它在同样较旧的Fedora 12版虚拟机上只符合C 98.此外,我们只遇到一个运行了6个月的代码库,就遇到了这个问题
最佳答案 如果不从任何其他并发线程访问adjVariable,那么它几乎保证在断言点为0.
虽然所有bool都是单个内存位置,并且确实可以在它们之间产生一些奇怪的行为,但adjVariable是一个单独的内存位置,编译器必须确保它的所有加载和存储看起来都按照定义良好的顺序发生,符合源代码.
如果编译器为位字段发出64位写入,则必须通过将位字段与8字节对齐来保护相邻存储器位置(例如,adjVariable和flag1之间应该有7字节填充).我不知道64位读取如何在这里弄错正确.
虽然内存位置的概念仅适用于C 11及更高版本,但逻辑仍适用于C 98:在assert中,adjVariable不能为零的唯一方法应该是另一个线程写入adjVariable.