我有一个任务来实现一个非常基本的无锁排序向量(只有插入和索引),并且我一切正常,但是,valgrind 说我有一个条件跳转/移动取决于未初始化的值。我正在使用 --track-origins=yes,但我发现它没有那么有用。
这是我的索引运算符的代码:
int operator[](int pos) {
Pair pdata_old = pdata.load();
Pair pdata_new = pdata_old;
// Increment ref count
do {
pdata_new = pdata_old;
++pdata_new.ref_count;
} while (!pdata.compare_exchange_weak(pdata_old, pdata_new));
// Get old data
int ret_val = (*pdata_new.pointer)[pos];
pdata_old = pdata.load();
// Decrement ref count
do {
pdata_new = pdata_old;
--pdata_new.ref_count;
// assert(pdata_new.ref_count >= 0);
} while (!pdata.compare_exchange_weak(pdata_old, pdata_new));
return ret_val;
}
Pair 只是一个包含向量* 和 int 的结构,构造函数初始化其所有值。我找不到任何依赖未初始化数据的地方,至少仅通过查看我的代码。
下面是相关的 valgrind 输出(第 121 行是声明函数的行,130 和 142 是 compare_exchange_weak() 行):
==21299==
==21299== Thread 2:
==21299== Conditional jump or move depends on uninitialised value(s)
==21299== at 0x10A5C2: LFSV::operator[](int) (lfsv.h:130)
==21299== by 0x1099F4: read_position_0() (driver.cpp:27)
==21299== by 0x10FCC6: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (invoke.h:60)
==21299== by 0x10FC5C: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (invoke.h:95)
==21299== by 0x10FC34: _ZNSt6thread8_InvokerISt5tupleIJPFvvEEEE9_M_invokeIJLm0EEEEDTclsr3stdE8__invokespcl10_S_declvalIXT_EEEEESt12_Index_tupleIJXspT_EEE (thread:234)
==21299== by 0x10FC04: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (thread:243)
==21299== by 0x10FAE8: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (thread:186)
==21299== by 0x50FAB9E: execute_native_thread_routine (thread.cc:83)
==21299== by 0x593208B: start_thread (in /usr/lib/libpthread-2.26.so)
==21299== by 0x5C3EE7E: clone (in /usr/lib/libc-2.26.so)
==21299== Uninitialised value was created by a stack allocation
==21299== at 0x10A520: LFSV::operator[](int) (lfsv.h:121)
==21299==
==21299== Conditional jump or move depends on uninitialised value(s)
==21299== at 0x10A654: LFSV::operator[](int) (lfsv.h:142)
==21299== by 0x1099F4: read_position_0() (driver.cpp:27)
==21299== by 0x10FCC6: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (invoke.h:60)
==21299== by 0x10FC5C: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (invoke.h:95)
==21299== by 0x10FC34: _ZNSt6thread8_InvokerISt5tupleIJPFvvEEEE9_M_invokeIJLm0EEEEDTclsr3stdE8__invokespcl10_S_declvalIXT_EEEEESt12_Index_tupleIJXspT_EEE (thread:234)
==21299== by 0x10FC04: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (thread:243)
==21299== by 0x10FAE8: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (thread:186)
==21299== by 0x50FAB9E: execute_native_thread_routine (thread.cc:83)
==21299== by 0x593208B: start_thread (in /usr/lib/libpthread-2.26.so)
==21299== by 0x5C3EE7E: clone (in /usr/lib/libc-2.26.so)
==21299== Uninitialised value was created by a stack allocation
==21299== at 0x10A520: LFSV::operator[](int) (lfsv.h:121)
==21299==
这是正常的,在带有填充的对象上使用compare_exchange_weak
时无需担心。 它可能会导致虚假的 CAS 故障,因此如果您只使用一次compare_exchange_strong
而没有重试循环或其他东西,请担心。
Pair 只是一个包含向量* 和 int
的结构
因此在正常的 64 位C++实现上填充,其中sizeof(vector*) == 8
和sizeof(int) == 4
,以及alignof(vector*) == 8
.
要使每个指针成员 8 字节对齐,整个结构/类需要对齐 8,因此其大小必须填充到 8 的倍数,以便Pair foo[]
数组正常工作,每个数组元素具有 8 字节对齐。
但compare_exchange_weak
比较整个对象的位模式,包括填充。
假设您在没有优化的情况下进行了编译,并且编译器制作了将局部变量存储到堆栈的代码,其中包含int
成员的 4 字节存储,但随后使用两个 8 字节加载将其加载回整个Pair
,用于 x86-64 的cmpxchg16b
指令,或者(如果atomic<Pair>
不是无锁的)获取锁并有效地执行memcmp
。