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