标准C++类std::string的内存共享和Copy-On-Write(写时拷贝)

简介:

1.关于string的内存共享问题:

通常,string类中必有一个私有成员,其是一个char*,用户记录从堆上分配内存的地址,其在构造时分配内存,在析构时释放内存。

因为是从堆上分配内存,所以string类在维护这块内存上是格外小心的,string类在返回这块内存地址时,只返回const char*,也就是只读的,

如果你要写,也只能通过string提供的方法进行数据的改写。

[cpp]  view plain  copy 在CODE上查看代码片派生到我的代码片
  1. #include<iostream>  
  2. #include<string>  
  3. #include<cstdio>  
  4. using namespace std;  
  5.    
  6. main()  
  7. {  
  8.        string str1 = "hello world";  
  9.        string str2 = str1;  
  10.        string str3 = str2;  
  11.     
  12.        printf ("内存共享:\n");  
  13.        printf ("\tstr1 的地址: %x\n", (unsigned int)str1.c_str() );  
  14.        printf ("\tstr2 的地址: %x\n", (unsigned int)str2.c_str() );  
  15.        printf ("\tstr3 的地址: %x\n", (unsigned int)str3.c_str() );  
  16.   
  17.        return 0;  
  18. }  
如上例子中,str1,str2,str3共享同一块内存,如图:


基本就是内存string类内存共享的最底层展现了,既然内存是一样的了,如果需要改写某个对象怎么办?由此引出写时拷贝Copy-On-Write

2.关于Copy-On-Write(原理)

顾名思义,写的时候在拷贝,(读的时候就不用了,哈哈)

还是以上边的例子为例:

[cpp]  view plain  copy 在CODE上查看代码片派生到我的代码片
  1. #include<iostream>  
  2. #include<string>  
  3. #include<cstdio>  
  4. using namespace std;  
  5.    
  6. main()  
  7. {  
  8.        string str1 = "hello world";  
  9.        string str2 = str1;  
  10.        string str3 = str2;  
  11.     
  12.        printf ("内存共享:\n");  
  13.        printf ("\tstr1 的地址: %x\n", (unsigned int)str1.c_str() );  
  14.        printf ("\tstr2 的地址: %x\n", (unsigned int)str2.c_str() );  
  15.        printf ("\tstr3 的地址: %x\n", (unsigned int)str3.c_str() );  
  16.   
  17.        str3[1]='a';  
  18.        str2[1]='w';  
  19.        str1[1]='q';  
  20.     
  21.        printf ("通过写时拷贝之后:\n");  
  22.        printf ("\tstr1 的地址: %x\n", (unsigned int)str1.c_str() );  
  23.        printf ("\tstr2 的地址: %x\n", (unsigned int)str2.c_str() );  
  24.        printf ("\tstr3 的地址: %x\n", (unsigned int)str3.c_str() );  
  25.   
  26.        return 0;  
  27. }  
  28.   
  29. //输出结果:  
  30. 内存共享:  
  31.   str1 的地址: 83f9017  
  32.   str2 的地址: 83f9017  
  33.   str3 的地址: 83f9017  
  34. 通过写时拷贝之后:  
  35.   str1 的地址: 83f9017  
  36.   str2 的地址: 83f9054  
  37.   str3 的地址: 83f9034  
很明显可以看出来,一开始,str1,str2,str3共享同一块内存,地址都是一样的;

当开始修改是这些内存是,先不说如何实现,先表征是如何写时拷贝的,看图,咱还是看图:

图中依然说明了str3的内容修改是怎么回事,str2的内容修改,也是同样的道理,重新给str2在堆上开辟空间,原空间只是str1一个人用,修改最后一个str1的内容时,

当然就不用在和前两种一样啦,因为,这个时候,原空间只有str1一个人用,这个时候,对此空间操作,没有任何问题。都写都可以;

写时拷贝在此例中的体现,主要是str2,和str3内容的修改;但是有没有发现,我每次开辟空间的同时,会在新开辟的空间开头多分配一个空间,存放的是count;

原因就和写时拷贝的具体操作有关了:

3.写时拷贝(Copy-On-Write)的实现:

Copy-On-Write使用了“引用计数”,有一个变量count来计数,而且计数就放在没开辟一段空间的开头几个字节。

当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,

当有类析构时,这个计数会减一,直到最后一个类析构时,此时的count为1或是0,此时,程序才会真正的Free这块从堆上分配的内存。 

下面是我写的一个简单的例子:

[cpp]  view plain  copy 在CODE上查看代码片派生到我的代码片
  1. #include<iostream>  
  2. using namespace std;  
  3.   
  4. class String  
  5. {  
  6. public:  
  7.     String(const char* str)  
  8.         //初始时字符创有一个\0外加4个字节的引用计数空间  
  9.         :_str(new char[strlen(str)+5])  
  10.     {  
  11.         (*((int*)_str)) = 1;//申请的空间赋值为1  
  12.         _str += 4; //让_str还是指向字符创的第一个字符  
  13.                    //而不是引用计数的头上  
  14.         strcpy(_str,str);  
  15.     }  
  16.   
  17.     String(const String& s)  
  18.         :_str(s._str)  
  19.     {  
  20.         (*(((int*)_str) - 1)) += 1;  
  21.     }  
  22.   
  23.     String& operator=(const String& s)  
  24.     {  
  25.         if(_str != s._str)  
  26.         {  
  27.             if(*(((int*)_str) - 1) == 0)  
  28.             {  
  29.                 delete[] (_str-4);  
  30.             }  
  31.             _str = s._str;  
  32.             *(((int*)_str) - 1) += 1;  
  33.         }  
  34.         return *this;  
  35.   
  36.   
  37.     }  
  38.     ~String()  
  39.     {  
  40.         if(*(((int*)_str) - 1) == 0)  
  41.         {  
  42.             _str -= 4;  
  43.             delete[] _str;  
  44.         }  
  45.     }  
  46. private:  
  47.     char *_str;  
  48. };  
  49.   
  50. void Test()  
  51. {  
  52.     String s1("11111111111111111111111111");  
  53.     String s2(s1);  
  54. }  
  55.   
  56. int main()  
  57. {  
  58.     Test();  
  59.     return 0;  
  60. }  
在内存开头开辟引用计数空间;

到此,string类的内存共享和写时拷贝,就算是告一段落了,个人拙见,跪求赐教!


本文转自莫水千流博客园博客,原文链接:http://www.cnblogs.com/zhoug2020/p/6542286.html,如需转载请自行联系原作者

相关文章
|
4月前
|
Go 开发者
Go语言内存共享与扩容机制 -《Go语言实战指南》
本文深入探讨了Go语言中切片的内存共享机制与自动扩容策略。切片作为动态数组的抽象,其底层结构包含指针、长度和容量。多个切片可能共享同一底层数组,修改一个切片可能影响其他切片。当切片容量不足时,`append`会触发扩容,新容量按指数增长以优化性能。为避免共享导致的副作用,可通过`copy`创建独立副本或在函数中使用只读方式处理。最后总结了最佳实践,帮助开发者高效使用切片,写出更优代码。
115 10
|
3月前
|
存储 编译器 C语言
关于string的‘\0‘与string,vector构造特点,反迭代器与迭代器类等的讨论
你真的了解string的'\0'么?你知道创建一个string a("abcddddddddddddddddddddddddd", 16);这样的string对象要创建多少个对象么?你知道string与vector进行扩容时进行了怎么的操作么?你知道怎么求Vector 最大 最小值 索引 位置么?
76 0
|
6月前
|
缓存 安全 Java
《从头开始学java,一天一个知识点》之:字符串处理:String类的核心API
🌱 **《字符串处理:String类的核心API》一分钟速通!** 本文快速介绍Java中String类的3个高频API:`substring`、`indexOf`和`split`,并通过代码示例展示其用法。重点提示:`substring`的结束索引不包含该位置,`split`支持正则表达式。进一步探讨了String不可变性的高效设计原理及企业级编码规范,如避免使用`new String()`、拼接时使用`StringBuilder`等。最后通过互动解密游戏帮助读者巩固知识。 (上一篇:《多维数组与常见操作》 | 下一篇预告:《输入与输出:Scanner与System类》)
139 11
|
6月前
|
Java
课时14:Java数据类型划分(初见String类)
课时14介绍Java数据类型,重点初见String类。通过三个范例讲解:观察String型变量、&quot;+&quot;操作符的使用问题及转义字符的应用。String不是基本数据类型而是引用类型,但使用方式类似基本类型。课程涵盖字符串连接、数学运算与字符串混合使用时的注意事项以及常用转义字符的用法。
144 9
|
6月前
|
存储 JavaScript Java
课时44:String类对象两种实例化方式比较
本次课程的主要讨论了两种处理模式在Java程序中的应用,直接赋值和构造方法实例化。此外,还讨论了字符串池的概念,指出在Java程序的底层,DOM提供了专门的字符串池,用于存储和查找字符串。 1.直接赋值的对象化模式 2.字符串池的概念 3.构造方法实例化
|
10月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
281 2
|
12月前
|
Java 索引
java基础(13)String类
本文介绍了Java中String类的多种操作方法,包括字符串拼接、获取长度、去除空格、替换、截取、分割、比较和查找字符等。
135 0
java基础(13)String类
|
11月前
|
Java
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
本文深入探讨了Java中方法参数的传递机制,包括值传递和引用传递的区别,以及String类对象的不可变性。通过详细讲解和示例代码,帮助读者理解参数传递的内部原理,并掌握在实际编程中正确处理参数传递的方法。关键词:Java, 方法参数传递, 值传递, 引用传递, String不可变性。
217 1
【编程基础知识】(讲解+示例实战)方法参数的传递机制(值传递及地址传递)以及String类的对象的不可变性
|
12月前
|
安全 Java
String类-知识回顾①
这篇文章回顾了Java中String类的相关知识点,包括`==`操作符和`equals()`方法的区别、String类对象的不可变性及其好处、String常量池的概念,以及String对象的加法操作。文章通过代码示例详细解释了这些概念,并探讨了使用String常量池时的一些行为。
String类-知识回顾①
|
11月前
|
安全 Java 测试技术
Java零基础-StringBuffer 类详解
【10月更文挑战第9天】Java零基础教学篇,手把手实践教学!
280 2