从理论上讲,我可以这么说
free(ptr);
free(ptr);
Run Code Online (Sandbox Code Playgroud)
是一个内存损坏,因为我们释放已经释放的内存.
但是如果
free(ptr);
ptr=NULL;
free(ptr);
Run Code Online (Sandbox Code Playgroud)
由于操作系统将以不确定的方式运行,我无法对此进行实际的理论分析.无论我在做什么,这种记忆是否会腐败?
释放空指针有效吗?
我编写了一个简单的“信封”类,以确保我正确理解C ++ 11原子语义。我有一个标头和一个有效负载,编写器清除该标头,填充有效负载,然后用递增的整数填充标头。这样的想法是,读取器然后可以读取标头,将有效负载换出,再次读取标头,如果标头相同,则读取器可以假定他们成功复制了有效负载。读者可能会错过一些更新是可以的,但是让他们获得更新的撕裂(其中来自不同更新的字节混合在一起)也不是可以的。永远只有一个读者和一个作家。
编写者使用释放内存顺序,而读者使用获取内存顺序。
是否存在通过原子存储/加载调用对memcpy重新排序的风险?还是可以将负载彼此重新排序?这永远不会让我流产,但也许我很幸运。
#include <iostream>
#include <atomic>
#include <thread>
#include <cstring>
struct envelope {
alignas(64) uint64_t writer_sequence_number = 1;
std::atomic<uint64_t> sequence_number;
char payload[5000];
void start_writing()
{
sequence_number.store(0, std::memory_order::memory_order_release);
}
void publish()
{
sequence_number.store(++writer_sequence_number, std::memory_order::memory_order_release);
}
bool try_copy(char* copy)
{
auto before = sequence_number.load(std::memory_order::memory_order_acquire);
if(!before) {
return false;
}
::memcpy(copy, payload, 5000);
auto after = sequence_number.load(std::memory_order::memory_order_acquire);
return before == after;
}
};
envelope g_envelope;
void reader_thread()
{
char local_copy[5000];
unsigned messages_received = 0;
while(true) {
if(g_envelope.try_copy(local_copy)) {
for(int i …Run Code Online (Sandbox Code Playgroud) 以下摘自Concurrent Programming on windows,第10章第528~529页,一个c++模板Double check实现
T getValue(){
if (!m_pValue){
EnterCriticalSection(&m_crst);
if (! m_pValue){
T pValue = m_pFactory();
_WriteBarrier();
m_pValue = pValue;
}
LeaveCriticalSection(&m_crst);
}
_ReadBarrier();
return m_pValue;
}
Run Code Online (Sandbox Code Playgroud)
正如作者所说:
在实例化对象之后,但在 m_pValue 字段中写入指向它的指针之前,会找到 _WriteBarrier。这是确保对象初始化中的写入永远不会延迟到对 m_pValue 本身的写入之后所必需的。
由于_WriteBarrier 是编译屏障,如果编译知道LeaveCriticalSection 的语义,我认为它没有用。编译可能会省略对 pValue 的写入,但永远不会优化在函数调用之前移动赋值,否则会违反程序语义。我相信 LeaveCriticalSection 具有隐式硬件围栏。因此,在分配给 m_pValue 之前的任何写入都将被同步。
另一方面,如果编译不知道 LeaveCriticalSection 的语义,则所有平台都需要 _WriteBarrier以防止编译将赋值移出临界区。
而对于_ReadBarrier,作者说
类似地,我们在返回 m_value 之前需要一个 _ReadBarrier,以便调用 getValue 之后的加载不会重新排序在调用之前发生。
首先,如果这个函数包含在一个库中,并且没有可用的源代码,那么编译如何知道是否存在编译障碍?
其次,如果需要,它会被放置在错误的位置,我认为我们需要将它放在 EnterCriticalSection 之后以表达获取栅栏。与我上面写的类似,这取决于编译是否理解 EnterCriticalSection 的语义。
而且作者还说:
但是,我还要指出,X86、Intel64 和 AMD64 处理器都不需要栅栏。不幸的是,像 IA64 这样的弱处理器已经把水搅浑了
正如我上面分析的,如果我们在某个平台上需要那些屏障,那么我们在所有平台上都需要它们,因为那些屏障是编译屏障,它只是确保编译可以做正确的优化,以防万一他们不明白一些函数的语义。
如果我错了,请纠正我。
另一个问题,msvc 和 gcc 有什么参考可以指出他们理解同步语义的函数吗? …