目录
写时拷贝原理
原文:
http://c.biancheng.net/view/1272.html
什么是写时拷贝
写时拷贝(copy-on-write, COW)就是等到修改数据时才真正分配内存空间,这是对程序性能的优化,可以延迟甚至是避免内存拷贝,当然目的就是避免不必要的内存拷贝。
典型例子:
在 Linux 系统中,调用 fork
系统调用创建子进程时,并不会把父进程所有占用的内存页复制一份,而是与父进程共用相同的内存页,而当子进程或者父进程对内存页进行修改时才会进行复制 —— 这就是著名的 写时复制
机制。
(也就是只有进程空间的某页内存的内容要发生变化时,才会将父进程的该页内存复制一份给子进程。)
这些共享页面标记为写时复制,这意味着如果任何一个进程写入共享页面,那么就创建共享页面的副本,写时复制如图 1 所示,图中分别反映了修改页面 C 的前与后。
写时拷贝原理
写时拷贝技术实际上是运用了一个 “引用计数” 的概念来实现的。在开辟的空间中多维护四个字节来存储引用计数。
有两种方法:
①:多开辟四个字节(pCount)的空间,用来记录有多少个指针指向这片空间。
②:在开辟空间的头部预留四个字节的空间来记录有多少个指针指向这片空间。
当我们多开辟一份空间时,让引用计数+1,如果有释放空间,那就让计数-1,但是此时不是真正的释放,是假释放,等到引用计数变为 0 时,才会真正的释放空间。如果有修改或写的操作,那么也让原空间的引用计数-1,并且真正开辟新的空间。
linux 下的 fork() 就是用的写时拷贝技术,引用计数不光在 string 这里用到,还有智能指针 shared_ptr 也用到了引用计数来解决拷贝问题。
举个例子
string 的写时拷贝(维护一个指针):
class String
{
public:
//构造
String(const char* str)
:_str(new char[strlen(str) + 1])
,_pCount(new int(1))
{
strcpy(_str, str);
}
//拷贝构造
String(const String& s)
:_str(s._str)
,_pCount(s._pCount)
{
(*_pCount)++;
}
//赋值运算符重载
String& operator=(const String& s)
{
if(_str != s._str)
{
if(--(*_pCount) == 0)
{
delete[] _str;
delete _pCount;
}
_str = s._str;
_pCount = s._pCount;
(*_pCount)++;
}
return *this;
}
~String()
{
if(--(*_pCount) == 0)
{
delete[] _str;
delete _pCount;
}
}
char& operator[](size_t pos)
{
if(*_pCount > 1)
{
char* newstr = new char[strlen(_str) + 1];
strcpy(newstr, _str);
--(*_pCount);
_str = newstr;
_pCount = new int(1);
}
return _str[pos];
}
const char* c_str()
{
return _str;
}
private:
char* _str;
int* _pCount;
};
源码中的写法:在空间的头部维护四个字节的空间,记录引用的个数。放在头部维护效率能高一些,如果放在尾部维护的话,每次开辟新的空间都要讲这四个字节也向后挪动相应的位置,所以放在前面效率高点
class String
{
public:
//构造
String(const char* str)
:_str(new char[strlen(str) + 4 + 1])
{
_str += 4; //前四个字节放引用计数
strcpy(_str, str);
GetRefCount() = 1;
}
//拷贝构造
String(const String& s)
:_str(s._str)
{
GetRefCount()++;
}
//赋值运算符重载
String& operator=(const String& s)
{
if(_str != s._str)
{
if(--GetRefCount() == 0)
{
delete[] (_str - 4);
}
_str = s._str;
GetRefCount()++;
}
return *this;
}
~String()
{
if(--GetRefCount() == 0)
{
delete[] (_str - 4);
_str = nullptr;
}
}
char& operator[](size_t pos)
{
if(GetRefCount() > 1)
{
--GetRefCount();
char* newstr = new char[strlen(_str) + 4 + 1];
newstr += 4;
strcpy(newstr, _str);
_str = newstr;
GetRefCount() = 1;
}
return _str[pos];
}
int& GetRefCount()
{
return *((int*)(_str - 4)); //前四个字节为引用计数
}
private:
char* _str;
};