内存共享与写时拷贝 原创 C++开发 2022年3月9日 17:07 夏至未至 1057 当前内容 3588 字,在路上,马上到,马上到 ### 目录 [TOC] ### 内存共享 #### 何意 内存共享,就是更多对象共同使用一块内存,共享这块内存 #### 举例 以 标准C++类 `std::string` 为例,通常,`string`类中必有一个私有成员,其是一个`char*`,用以记录从堆上分配内存的地址,其在构造时分配内存,在析构时释放内存。因为是从堆上分配内存,所以`string`类在维护这块内存上是格外小心的,`string`类在返回这块内存地址时,只返回`const char*`,也就是只读的,如果你要写,也只能通过`string`提供的方法进行数据的改写。 #### 共享验证 #include #include #include using namespace std; main() { string str1 = "hello world"; string str2 = str1; string str3 = str2; printf ("内存共享:\n"); printf ("\tstr1 的地址: %x\n", (unsigned int)str1.c_str() ); printf ("\tstr2 的地址: %x\n", (unsigned int)str2.c_str() ); printf ("\tstr3 的地址: %x\n", (unsigned int)str3.c_str() ); return 0; } // 输出 内存共享: str1 的地址: 83f9017 str2 的地址: 83f9017 str3 的地址: 83f9017 如上例子中,str1,str2,str3共享同一块内存,如图:  基本就是内存`string`类内存共享的最底层展现了,既然内存是一样的了,如果需要改写某个,其他对象必然也改变,必然不可取,由此引出写时拷贝`Copy-On-Write` ### 写时拷贝 #### 何意 Copy-On-Write:顾名思义,写的时候在拷贝,就是等到修改数据时才真正分配内存空间,这是对程序性能的优化,可以延迟甚至是避免内存拷贝,目的就是避免不必要的内存拷贝。 #### 验证 #include #include #include using namespace std; main() { string str1 = "hello world"; string str2 = str1; string str3 = str2; printf ("内存共享:\n"); printf ("\tstr1 的地址: %x\n", (unsigned int)str1.c_str() ); printf ("\tstr2 的地址: %x\n", (unsigned int)str2.c_str() ); printf ("\tstr3 的地址: %x\n", (unsigned int)str3.c_str() ); str3[1]='a'; str2[1]='w'; str1[1]='q'; printf ("通过写时拷贝之后:\n"); printf ("\tstr1 的地址: %x\n", (unsigned int)str1.c_str() ); printf ("\tstr2 的地址: %x\n", (unsigned int)str2.c_str() ); printf ("\tstr3 的地址: %x\n", (unsigned int)str3.c_str() ); return 0; } // 输出 内存共享: str1 的地址: 83f9017 str2 的地址: 83f9017 str3 的地址: 83f9017 通过写时拷贝之后: str1 的地址: 83f9017 str2 的地址: 83f9054 str3 的地址: 83f9034 很明显可以看出来,一开始,str1,str2,str3共享同一块内存,地址都是一样的。运行结果告诉我们,str1、str2与str3是共享内存空间的(`char*`成员指向相同的地址),但是在给string赋值时,地址都变了,这是因为触发了写时拷贝。 #### 原理 当开始修改是这些内存是,先不说如何实现,先表征是如何写时拷贝的,如图:  图中依然说明了str3的内容修改是怎么回事,str2的内容修改,也是同样的道理,重新给str2在堆上开辟空间,原空间只是str1一个人用,修改最后一个str1的内容时,当然就不用在和前两种一样啦,因为,这个时候,原空间只有str1一个人用,这个时候,对此空间操作,没有任何问题。都写都可以; 写时拷贝在此例中的体现,主要是str2,和str3内容的修改;但是有没有发现,我每次开辟空间的同时,会在新开辟的空间开头多分配一个空间,存放的是count;原因就和写时拷贝的实现有关了。 #### 实现 Copy-On-Write使用了 `引用计数`,用一个变量count来计数,而且计数就放在每开辟一段空间的开头几个字节。当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构时,这个计数会减一,直到最后一个类析构时,此时的count为1或是0,此时,程序才会真正的Free这块从堆上分配的内存。 如下为模拟实现string类的写时拷贝过程: #include using namespace std; class String { public: String(const char* str) //初始时字符创有一个\0外加4个字节的引用计数空间 :_str(new char[strlen(str)+5]) { (*((int*)_str)) = 1;//申请的空间赋值为1 _str += 4; //让_str还是指向字符创的第一个字符 //而不是引用计数的头上 strcpy(_str,str); } String(const String& s) :_str(s._str) { (*(((int*)_str) - 1)) += 1; } String& operator=(const String& s) { if(_str != s._str) { if(*(((int*)_str) - 1) == 0) { delete[] (_str-4); } _str = s._str; *(((int*)_str) - 1) += 1; } return *this; } ~String() { if(*(((int*)_str) - 1) == 0) { _str -= 4; delete[] _str; } } private: char *_str; }; void Test() { String s1("11111111111111111111111111"); String s2(s1); } int main() { Test(); return 0; } 本文标题: 内存共享与写时拷贝 本文作者: 夏至未至 发布时间: 2022年3月9日 17:07 最近更新: 2022年3月9日 17:36 原文链接: 许可协议: 署名-非商业性-禁止演绎 4.0 国际(CC BY-NC-ND 4.0) 请按协议转载并保留原文链接及作者 Copy-On-Write(1) 上一个 Another app is currently holding the yum lock 已解决 下一个 MySQL整实例逻辑备份脚本 当前文章评论暂未开放,请移步至留言处留言。