The same instances of the same class but different behaviour. Probable UB

拜拜、爱过 提交于 2020-01-05 04:22:07

问题


#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. It compiled and executed without error. But, there is still problem, which I described below.

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

On my eye, result it is not expected: NOTEQUALS EQUALS

I have a wild problem with above piece of code.

Especially, the comparison in the line $1 makes me a problem. I mean, this comparison always returns false though it should returns true at the first time.

I was confused so I look into memory for empty and head and actually they are different. head is equal to 0x00000000 0x00000000 0x00000000 0x00000000 ( when it comes to bytes) and it seems to be OK. But empty is equal to: 0x00000000 0x00000000 0x00000000 0x7f7f7f7f7f. What is more interesting next in the $2 is equal to 0x00000000 0x00000000 0x00000000 0x00000000 so in fact, it is equals to head. But, for example, curr, incrementedNext are equal to 0x00000000 0x00000000 0x00000000 0x7f7f7f7f7f. So the behaviour of that is undeterministic so I suppose any undefined behaviour, but why? What I do not correctly, please explain me this behaviour.

P.S. I know about memory leak in the $4 but now I'm ignoring it.

I compiled it with: g++ -latomic main.cpp -std=c++14. My version of gcc is 6.1.0. I tested on gcc 5.1.0 as well. The result is same.

The link to the source created by @PeterCordes: https://godbolt.org/g/X02QV8


回答1:


Padding. std::atomic::compare_exchange* compares memory representation of the two objects, as if by memcmp. If the structure has paddding, its contents are indeterminate, and may make two instances look distinct even if they are member-wise equal (note that CountedNode doesn't even define operator==).

In 64-bit build, there's padding after counter, and you see the issue. In 32-bit build, there isn't, and you don't.

EDIT: the part below is, I now believe, wrong; only preserved for completeness. std::atomic_init doesn't do anything to zero out padding; the example only appears to work by accident.


head (as well as Node::next) should be initialized with std::atomic_init:

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

With that in place, your example works as expected



来源:https://stackoverflow.com/questions/38868311/the-same-instances-of-the-same-class-but-different-behaviour-probable-ub

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!