zoukankan      html  css  js  c++  java
  • 标准C++类std::string的内存共享和Copy-On-Write(写时拷贝)

    标准C++类std::string的内存共享,值得体会:

    详见大牛:https://www.douban.com/group/topic/19621165/

    顾名思义,内存共享,就是两个乃至更多的对象,共同使用一块内存;

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

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

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

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

    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 ("内存共享: ");  
    13.        printf (" str1 的地址: %x ", (unsigned int)str1.c_str() );  
    14.        printf (" str2 的地址: %x ", (unsigned int)str2.c_str() );  
    15.        printf (" str3 的地址: %x ", (unsigned int)str3.c_str() );  
    16.   
    17.        return 0;  
    18. }  
    如上例子中,str1,str2,str3共享同一块内存,如图:


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

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

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

    还是以上边的例子为例:

    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 ("内存共享: ");  
    13.        printf (" str1 的地址: %x ", (unsigned int)str1.c_str() );  
    14.        printf (" str2 的地址: %x ", (unsigned int)str2.c_str() );  
    15.        printf (" str3 的地址: %x ", (unsigned int)str3.c_str() );  
    16.   
    17.        str3[1]='a';  
    18.        str2[1]='w';  
    19.        str1[1]='q';  
    20.     
    21.        printf ("通过写时拷贝之后: ");  
    22.        printf (" str1 的地址: %x ", (unsigned int)str1.c_str() );  
    23.        printf (" str2 的地址: %x ", (unsigned int)str2.c_str() );  
    24.        printf (" str3 的地址: %x ", (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这块从堆上分配的内存。 

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

    1. #include<iostream>  
    2. using namespace std;  
    3.   
    4. class String  
    5. {  
    6. public:  
    7.     String(const char* str)  
    8.         //初始时字符创有一个外加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类的内存共享和写时拷贝,就算是告一段落了,个人拙见,跪求赐教!

  • 相关阅读:
    Java微信二次开发(八)
    Java微信二次开发(七)
    Java微信二次开发(六)
    Java微信二次开发(五)
    Java微信二次开发(四)
    Java微信二次开发(三)
    IIS中X509Certificate遇见的问题
    SQL Server 数据库定时自动备份
    ASP.NET 5 (vNext) 理解和概述
    ASP.NET 5 (vNext) Linux部署
  • 原文地址:https://www.cnblogs.com/zhoug2020/p/6542286.html
Copyright © 2011-2022 走看看