Valgrind:条件跳转或移动取决于使用 atomic::compare_exchange_weak 时未初始化的值

Valgrind: Conditional jump or move depends on uninitialised value(s) when using atomic::compare_exchange_weak

我有一个任务要实现一个非常基本的无锁排序向量(只有插入和索引),并且我一切正常,但是,valgrind 说我有一个条件 jump/move 取决于在一个未初始化的值上。我正在使用 --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 只是一个包含 vector* 和 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 is just a struct that contains a vector* and an int

因此在普通的 64 位 C++ 实现上有填充,其中 sizeof(vector*) == 8sizeof(int) == 4,以及 alignof(vector*) == 8.

为了使每个指针成员8字节对齐,整个struct/class需要8对齐,因此它的大小必须填充到8的倍数,这样[=的数组15=] 工作正常,每个数组元素有 8 字节对齐。

但是compare_exchange_weak比较的是整个对象的bit-pattern,包括padding。

大概你编译时没有优化,编译器生成的代码将局部变量存储到堆​​栈,为 int 成员使用 4 字节存储,然后将其加载回整个 Pair x86-64 的 cmpxchg16b 指令的两个 8 字节加载,或者(如果 atomic<Pair> 不是 lock-free)获取锁并有效地执行 memcmp.