博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
标准C++类string的Copy-On-Write技术(三)
阅读量:6203 次
发布时间:2019-06-21

本文共 3377 字,大约阅读时间需要 11 分钟。

2.3.4、      Copy-On-Write的具体实现是怎么样的?

 
最后的这个问题,我们主要解决的是那个“民主集中”的难题。请先看下面的代码:
 
string h1 = “hello”;
string h2= h1;
string h3;
h3 = h2;
 
string w1 = “world”;
string w2(“”);
w2=w1;
 
很明显,我们要让h1
h2
h3
共享同一块内存,让w1
w2
共享同一块内存。因为,在h1
h2
h3
中,我们要维护一个引用计数,在w1
w2
中我们又要维护一个引用计数。
 
如何使用一个巧妙的方法产生这两个引用计数呢?我们想到了string
类的内存是在堆上动态分配的,既然共享内存的各个类指向的是同一个内存区,我们为什么不在这块区上多分配一点空间来存放这个引用计数呢?这样一来,所有共享一块内存区的类都有同样的一个引用计数,而这个变量的地址既然是在共享区上的,那么所有共享这块内存的类都可以访问到,也就知道这块内存的引用者有多少了。
 
请看下图:
 
于是,有了这样一个机制,每当我们为string
分配内存时,我们总是要多分配一个空间用来存放这个引用计数的值,只要发生拷贝构造可是赋值时,这个内存的值就会加一。而在内容修改时,string
类为查看这个引用计数是否为0
,如果不为零,表示有人在共享这块内存,那么自己需要先做一份拷贝,然后把引用计数减去一,再把数据拷贝过来。下面的几个程序片段说明了这两个动作:
 
 
 
  //
构造函数(分存内存)
    string::string(const char* tmp)
{
    _Len = strlen(tmp);
    _Ptr = new char[_Len+1+1];
    strcpy( _Ptr, tmp );
    _Ptr[_Len+1]=0;  // 
设置引用计数
  
}
 
//
拷贝构造(共享内存)
    string::string(const string& str)
    {
         if (*this != str){
              this->_Ptr = str.c_str();   //
共享内存
              this->_Len = str.szie();
              this->_Ptr[_Len+1] ++;  //
引用计数加一
         }
}
 
//
写时才拷贝
Copy-On-Write
char& string::operator[](unsigned int idx)
{
    if (idx > _Len || _Ptr == 0 ) {
         static char nullchar = 0;
return nullchar;
          }
   
_Ptr[_Len+1]--;   //
引用计数减一
    char* tmp = new char[_Len+1+1];
    strncpy( tmp, _Ptr, _Len+1);
    _Ptr = tmp;
    _Ptr[_Len+1]=0; // 
设置新的共享内存的引用计数
   
    return _Ptr[idx];
}

 
//
析构函数的一些处理
~string()
{
 
_Ptr[_Len+1]--;   //
引用计数减一
   
         // 引用计数为0时,释放内存 
    if (_Ptr[_Len+1]==0) {
        delete[] _Ptr;
         }
 
}
 
哈哈,整个技术细节完全浮出水面。
 
不过,这和STL
basic_string
的实现细节还有一点点差别,在你打开STL
的源码时,你会发现其取引用计数是通过这样的访问:_Ptr[-1]
,标准库中,把这个引用计数的内存分配在了前面(我给出来的代码是把引用计数分配以了后面,这很不好),分配在前的好处是当string
的长度扩展时,只需要在后面扩展其内存,而不需要移动引用计数的内存存放位置,这又节省了一点时间。
 
STL
中的string
的内存结构就像我前面画的那个图一样,_Ptr
指着是数据区,而RefCnt
则在_Ptr-1 
或是 _Ptr[-1]
处。
 
 

2.4、         臭虫Bug

 
是谁说的“有太阳的地方就会有黑暗”?或许我们中的许多人都很迷信标准的东西,认为其是久经考验,不可能出错的。呵呵,千万不要有这种迷信,因为任何设计再好,编码再好的代码在某一特定的情况下都会有Bug
STL
同样如此,string
类的这个共享内存/
写时才拷贝技术也不例外,而且这个Bug
或许还会让你的整个程序crash
掉!
 
不信?!那么让我们来看一个测试案例:
 
假设有一个动态链接库(叫myNet.dll
myNet.so
)中有这样一个函数返回的是string
类:
 
string GetIPAddress(string hostname)
{
    static string ip;
    ……
    ……
    return ip;
}
 
 
而你的主程序中动态地载入这个动态链接库,并调用其中的这个函数:
 
 
main()
{
//
载入动态链接库中的函数
hDll = LoadLibraray(…..);
pFun =  GetModule(hDll, “GetIPAddress”);
 
//
调用动态链接库中的函数
string ip = (*pFun)(“host1”);
……
……
//
释放动态链接库
FreeLibrary(hDll);
……
cout << ip << endl;
}
 
 
 
让我们来看看这段代码,程序以动态方式载入动态链接库中的函数,然后以函数指针的方式调用动态链接库中的函数,并把返回值放在一个string
类中,然后释放了这个动态链接库。释放后,输入ip
的内容。
 
根据函数的定义,我们知道函数是“值返回”的,所以,函数返回时,一定会调用拷贝构造函数,又根据string
类的内存共享机制,在主程序中变量ip
是和函数内部的那个静态string
变量共享内存(这块内存区是在动态链接库的地址空间的)。而我们假设在整个主程序中都没有对ip
的值进行修改过。那么在当主程序释放了动态链接库后,那个共享的内存区也随之释放。所以,以后对ip
的访问,必然做造成内存地址访问非法,造成程序crash
。即使你在以后没有使用到ip
这个变量,那么在主程序退出时也会发生内存访问异常,因为程序退出时,ip
会析构,在析构时就会发生内存访问异常。
 
内存访问异常,意味着两件事:1
)无论你的程序再漂亮,都会因为这个错误变得暗淡无光,你的声誉也会因为这个错误受到损失。2
)未来的一段时间,你会被这个系统级错误所煎熬(在C++
世界中,找到并排除这种内存错误并不是一件容易的事情)。这是C/C++
程序员永远的心头之痛,千里之堤,溃于蚁穴。而如果你不清楚string
类的这种特征,在成千上万行代码中找这样一个内存异常,简直就是一场噩梦。
 
备注:要改正上述的Bug
,有很多种方法,这里提供一种仅供参考:
string ip = (*pFun)(“host1”).cstr();
 
 

3、             后记

 
文章到这里也应该结束了,这篇文章的主要有以下几个目的:
 
1)    
向大家介绍一下写时才拷贝/
内存共享这种技术。
2)    
STL
中的string
类为例,向大家介绍了一种设计模式。
3)    
C++
世界中,无论你的设计怎么精巧,代码怎么稳固,都难以照顾到所有的情况。智能指针更是一个典型的例子,无论你怎么设计,都会有非常严重的BUG
4)    C++
是一把双刃剑,只有了解了原理,你才能更好的使用C++
。否则,必将引火烧身。如果你在设计和使用类库时有一种“玩C++
就像玩火,必须千万小心”的感觉,那么你就入门了,等你能把这股“火”控制的得心应手时,那才是学成了。
 
最后,还是利用这个后序,介绍一下自己。我目前从事于所有Unix
平台下的软件研发,主要是做系统级的产品软件研发,对于下一代的计算机革命——网格计算非常地感兴趣,同于对于分布式计算、P2P
Web Service
J2EE
技术方向也很感兴趣,另外,对于项目实施、团队管理、项目管理也小有心得,希望同样和我战斗在“技术和管理并重”的阵线上的年轻一代,能够和我多多地交流。
本文转自 haoel 51CTO博客,原文链接:http://blog.51cto.com/haoel/124634,如需转载请自行联系原作者
你可能感兴趣的文章
创建邮箱过程中的问题及解决办法
查看>>
函数式编程概念
查看>>
安装输入发
查看>>
用户配置相关文件
查看>>
Django开发中常用的命令总结
查看>>
thinkphp实现分页
查看>>
自动化运维之saltstack(二)states深入理解
查看>>
变量拼接的四种方法
查看>>
expect 批量修改服务器用户密码
查看>>
linux下配置SS5(SOCK5)代理服务
查看>>
Nginx平滑升级到最新版本
查看>>
Python自动化运维:Django之View视图和Template
查看>>
hive配置
查看>>
Oracle Controlfile控制文件中记录的信息片段sections
查看>>
菜鸟也学hadoop(1)_搭建单节点的hadoop
查看>>
spanning-tree extend system-id
查看>>
思维导图分析http之http协议版本
查看>>
linux延时与定时操作
查看>>
JavaScript语法详解(三)
查看>>
老王学linux-ftp
查看>>