Те же экземпляры одного и того же класса, но различного поведения. Вероятность UB

#include <iostream>

#include <atomic>
#include <memory>


template<typename T>
class LockFreeQueue {
public:
    struct CountedNode;

private:
    std::atomic<CountedNode> head;
public:
    struct Node{
        explicit Node(const T& d) : next(CountedNode()), data(std::make_shared<T>(d)), node_counter(0) { }
        std::atomic<CountedNode> next;
        std::shared_ptr<T> data;
        std::atomic<unsigned> node_counter;
    };
    struct CountedNode {
        CountedNode() noexcept : node(nullptr), counter(0) {}
        explicit  CountedNode( const T& data) noexcept : node(new Node(data) /* $4 */), counter(0)  {}
        Node* node;
        int counter;
    };


    void push( const T& data)
    {
        CountedNode new_node(data), curr, incrementedNext, next /*($2) */;
        CountedNode empty; /*($3) */
        if (head.compare_exchange_strong(empty, new_node)) std::cout << "EQUALS\n"; // $1
        else std::cout << "NOT EQUALS\n";

        if (head.compare_exchange_strong(next, new_node)) std::cout << "EQUALS\n"; // $1
        else std::cout << "NOT EQUALS\n";
    }

};


int main() {
    LockFreeQueue<int> Q;
    Q.push(2);

    return 0;
}



    int main(){
    LockFreeQueue<int> Q;
    Q.push(2);

    return 0;
    }

Ok. Он скомпилирован и выполнен без ошибок. Но есть еще проблема, о которой я рассказывал ниже.

http://coliru.stacked-crooked.com/a/1fe71fafc5dde518

На мой взгляд, результат не ожидается:   NOTEQUALS   РАВНО

У меня есть дикая проблема с вышеуказанным фрагментом кода.

В частности, сравнение в строке $1 создает проблему. Я имею в виду, что это сравнение всегда возвращает false, хотя оно должно возвращать true в первый раз.

Я был смущен, поэтому я заглядываю в память для empty и head, и на самом деле они разные. head равен 0x00000000 0x00000000 0x00000000 0x00000000 (когда речь идет о байтах), и кажется, что все в порядке. Но empty равно: 0x00000000 0x00000000 0x00000000 0x7f7f7f7f7f. Что более интересно next в $2 равно 0x00000000 0x00000000 0x00000000 0x00000000, так что на самом деле оно равно head. Но, например, curr, incrementedNext равны 0x00000000 0x00000000 0x00000000 0x7f7f7f7f7f. Поэтому поведение этого не детерминировано, поэтому я предполагаю какое-либо поведение undefined, но почему? Что я не правильно, объясните мне это поведение.

P.S. Я знаю об утечке памяти в $4, но теперь я игнорирую это.

Я скомпилировал его с помощью: g++ -latomic main.cpp -std=c++14. Моя версия gcc - 6.1.0. Я тестировал также gcc 5.1.0. Результат такой же.

Ссылка на источник, созданный @PeterCordes: https://godbolt.org/g/X02QV8

Ответ 1

Перетяжка. std::atomic::compare_exchange* сравнивает представление памяти двух объектов, как будто memcmp. Если структура имеет paddding, ее содержимое является неопределенным и может сделать два экземпляра различимыми, даже если они по размеру равны (обратите внимание, что CountedNode даже не определяет operator==).

В 64-битной сборке есть пробел после counter, и вы видите проблему. В 32-битной сборке нет, а вы этого не делаете.

EDIT: приведенная ниже часть, я считаю, неверна; только для полноты. std::atomic_init ничего не делает для нулевого заполнения; пример, похоже, работает случайно.


head (а также Node::next) следует инициализировать с помощью std::atomic_init:

std::atomic_init(&head, CountedNode());

С этим на месте ваш пример работает как ожидалось