C++ 相同类的相同实例,但行为不同。可能的UB #包括 #包括

C++ 相同类的相同实例,但行为不同。可能的UB #包括 #包括,c++,c++11,x86,undefined-behavior,stdatomic,C++,C++11,X86,Undefined Behavior,Stdatomic,在我看来,结果并非如此: 记事本 相等于 我对上面的代码有一个很大的问题 特别是,$1行中的比较让我很头疼。我的意思是,这个比较总是返回false,尽管它在第一次应该返回true 我很困惑,所以我在内存中查找empty和head,实际上它们是不同的head等于0x00000000 0x00000000 0x00000000 0x00000000(当涉及到字节时)并且似乎是正常的。但为空等于: 0x00000000 0x00000000 0x00000000 0x7F7F7F7F。在$2中,下一个

在我看来,结果并非如此: 记事本 相等于

我对上面的代码有一个很大的问题

特别是,
$1
行中的比较让我很头疼。我的意思是,这个比较总是返回false,尽管它在第一次应该返回true

我很困惑,所以我在内存中查找
empty
head
,实际上它们是不同的
head
等于
0x00000000 0x00000000 0x00000000 0x00000000
(当涉及到字节时)并且似乎是正常的。但
为空
等于:
0x00000000 0x00000000 0x00000000 0x7F7F7F7F
。在
$2
中,下一个更有趣的
是等于
0x00000000 0x00000000 0x00000000 0x00000000
,所以实际上,它等于
头。但是,例如,
curr
incrementedNext
等于
0x00000000 0x00000000 0x00000000 0x7F7F7F
。 因此,这种行为是不确定的,所以我认为有任何未定义的行为,但为什么呢?如果我不正确,请解释我的这种行为

另外,我知道
$4
中存在内存泄漏,但现在我忽略了它

我是用以下方法编写的:
g++-latomic main.cpp-std=c++14
。 我的gcc版本是6.1.0。我也在gcc 5.1.0上进行了测试。结果是一样的


指向@PeterCordes:

创建的源的链接<代码>标准::原子::比较\u交换*
比较两个对象的内存表示,就像通过
memcmp
一样。如果结构具有填充,则其内容是不确定的,并且可能使两个实例看起来不同,即使它们在成员方面相等(请注意,
CountedNode
甚至没有定义
运算符==

在64位构建中,
计数器
后面有填充,您可以看到问题所在。在32位构建中,没有,您也没有

编辑:我现在认为,下面的部分是错误的;仅为完整性而保留
std::atomic_init
不做任何事情来消除填充;这个例子似乎只是偶然起作用


head
(以及
Node::next
)应使用
std::atomic_init
初始化:

#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;
    }

在这个位置上,填充<代码>标准::原子::比较\u交换*
比较两个对象的内存表示,就像通过
memcmp
一样。如果结构具有填充,则其内容是不确定的,并且可能使两个实例看起来不同,即使它们在成员方面相等(请注意,
CountedNode
甚至没有定义
运算符==

在64位构建中,
计数器
后面有填充,您可以看到问题所在。在32位构建中,没有,您也没有

编辑:我现在认为,下面的部分是错误的;仅为完整性而保留
std::atomic_init
不做任何事情来消除填充;这个例子似乎只是偶然起作用


head
(以及
Node::next
)应使用
std::atomic_init
初始化:

#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;
    }

有了它,

你就可以了。好吧,你可以一直编辑原始帖子,直到它是正确的。我只是复制/粘贴你的代码并编译它,它对我有用:。我可以建议您编写编译器版本和编译代码的命令吗?@Gilgamesz我刚刚用
g++-m32-g-o test test.cpp-latomic
编译了(gcc 6.1),它工作正常。使用调试器并查看内存时,请尝试启用调试标志(
-g
)。@BiagioFesta:Baseline x86-64没有
cmpxchg16b
,因此您需要使用
-mcx16
来启用它(或者它是作为
-march=haswell
的一部分启用的,或者在除最旧的CPU之外的任何CPU上启用的
-march=native
)。如果不这样做,gcc将发出对库函数的调用。你可以使用
-latomic
来避免链接错误。你可以。嗯,你可以一直编辑原始帖子,直到它正确。我只是复制/粘贴你的代码并编译它,它对我有用:。我可以建议您编写编译器版本和编译代码的命令吗?@Gilgamesz我刚刚用
g++-m32-g-o test test.cpp-latomic
编译了(gcc 6.1),它工作正常。使用调试器并查看内存时,请尝试启用调试标志(
-g
)。@BiagioFesta:Baseline x86-64没有
cmpxchg16b
,因此您需要使用
-mcx16
来启用它(或者它是作为
-march=haswell
的一部分启用的,或者在除最旧的CPU之外的任何CPU上启用的
-march=native
)。如果不这样做,gcc将发出对库函数的调用。您可以使用
-latomic
来避免链接错误。它不起作用。这个问题仍然存在。您的链接之所以有效,是因为填充的内容是不确定的,并且在您的代码段
empty
中的填充正好等于零。但是,如果您稍微更改堆栈的视图(请参阅链接我的意思,(35行)),您将再次看到
不等于
,请参阅:我尝试通过“手动”写入
清空
内存来解决此问题,我的意思是:1。在这种情况下,std::atomic_init是如何工作的?它是否有必要?2.如何以比手动写入填充更好的方式解决该问题?对。事实上,我怀疑
std::atomic_init
实际上没有任何帮助-它只是
memcpy
将其参数放入原子中,但没有任何内容表明该参数的填充已调零。因此,基本上,看起来像是
std::atomic
,其中
T
不是整数或指针类型是个坏主意。在为特定平台编写时,您可以以一种可能避免填充的方式设计结构(例如,对
计数器
使用
uintpttr\u t
,因此它的大小与指针相同,这将消除p