Chinaunix首页 | 论坛 | 博客
  • 博客访问: 58186
  • 博文数量: 11
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 140
  • 用 户 组: 普通用户
  • 注册时间: 2009-01-09 15:19
文章分类
文章存档

2017年(2)

2011年(1)

2010年(1)

2009年(7)

我的朋友

分类: C/C++

2017-03-16 00:01:48

    一晃竟然已经过去了五六年的光景,再次想起回到这里留下点滴记录,往日的博文似乎还是昨日写就一般。只是稍稍翻阅逐条评阅,顿觉愧对来此的读者,当初无声无息的就悄然远遁,辜负各位来此交流之意,想想实在汗颜,在此给苦等回复不得的同仁道一声歉,怠慢各位了。
    这两天想着自己编一编矩阵运算的程序,以便解决一些控制算法的开发需要。便在VS里面构建了一个类CMatrix,完了给这个类重载了诸多运算符,比如+、-、*之类的。而重载后的运算符大多要返回同样是CMatrix的对象,本来也是个天经地义的事情,却整出了幺蛾子,运行时出了问题。细一分析,才发现自己大意了,是内存上出现了问题。话休繁絮,先上了代码才是正事:

点击(此处)折叠或打开

  1. class CMatrix
  2. {
  3. public:
  4.     UINT r;
  5.     UINT c;
  6.     
  7.     double* e;
  8.     CMatrix(UINT row, UINT col);
  9.     ~CMatrix();
  10.     //重载运算符
  11.     CMatrix operator+(const CMatrix &B);
  12.     CMatrix operator-(const CMatrix &B);
  13.     CMatrix operator*(const CMatrix &B);
  14.     CMatrix operator*(const double &B);
  15.     CMatrix operator/(const double &B); 
  16.     ...
  17. }
    上面的r和c用来保存数组的行、列大小,e用来保存矩阵元素的值。很明显,因为矩阵的大小不固定,因此在构造函数里面要动态分配内存区给e来用。同样,在析构函数里面就要把这个分配的内存释放掉,才能保证程序不吃内存。构造函数和析构函数如下:

点击(此处)折叠或打开

  1. CMatrix::CMatrix(UINT row, UINT col)
  2. {
  3.     if (row != 0 && col != 0)
  4.     {
  5.         r = row;
  6.         c = col;

  7.         e = new double[r*c];
  8.         memset(e, 0, r*c*sizeof(double));
  9.     }
  10. }

  11. CMatrix::~CMatrix()
  12. {
  13.     r = 0;
  14.     c = 0;
  15.     if (e != NULL)
  16.     {
  17.      delete [] e;
  18.         e = NULL;
  19.     }
  20. }
    本来想着一个构造函数分配内存,一个析构函数释放内存,两个配合双剑合璧,天衣无缝了。不成想,一运行起来却被甩了好大一个耳刮子,竟然在重构的*操作符(此处是实现点乘的运算)爆出了非法内存访问的错误。代码也要贴上先:

点击(此处)折叠或打开

  1. CMatrix CMatrix::operator*(const double &B)
  2. {
  3.     int i = 0, j = 0;
  4.     CMatrix ans(r, c);

  5.     for (i = 0; i < r; i++)
  6.     {
  7.         for (j = 0; j < c; j++)
  8.         {
  9.             ans(i, j) = e[i*c+j]*B;
  10.         }
  11.     }

  12.     return ans;
  13. }
    代码很简单,却爆出了内存访问的错误,加断点调试,最后却发现问题出在析构函数里面delete语句。一时间感觉还真的很懵,没捋清是个什么状况。delete出问题的话,应该就是访问了不该访问的地方。既然动态分配的内存不可能是不能访问的区域,那么就只有可能是这个内存区已经释放过了,出现了重复释放的问题。但是怎么会出现重复释放呢,每个对象只构造一次,也只析构一次,从哪里出现的重复释放呢?
    不过仔细分析,却慢慢回过味来了,其实问题出现在重载操作符函数的返回上了。由于ans是在重载函数里面定义的局部变量,因此在函数返回的时候就会调用析构函数释放其成员变量e所指向的内存区,这是没有问题的。但是问题在于重载函数在返回的时候,会要把ans的内容返回给上级代码,这时候其实返回了另外一个CMatrix的对象,而且这个对象是完全拷贝了ans的。因为是完全拷贝,所以这个对象里面的成员变量e指向的也是ans对象所占用的内存区。那么当这个对象返回给上级代码之后,不管上级代码作何处理,这个对象都会在返回后释放,这时候它就会要调用析构函数去释放它的成员变量e所指的内存区。而很不幸,这个内存区已经被ans所释放了,这样就导致了重复释放的问题。
    发现问题所在,那么接下来应该就是动手解决这个问题。不过这个时候却犯了难了,原因很简单,因为我无法分辨什么时候是释放我自己定义的ans对象,什么时候是释放返回值所拷贝的对象。对于类里面所定义的所有标志,在ans和返回对象里面都是一模一样的,简直就是真假美猴王。思来想去,似乎只有利用共享内存的信号量来实现对所分配的内存进行保护,才能保证这两个真假美猴王能够相安无事地对这片内存进行访问。不过,为了解决这个问题要兴师动众地请出信号量这尊大佛帮忙,感觉挺麻烦的,实在是感觉困顿不想动手。于是想想还是求助度娘吧,别说还真让我找到了这个相对简单的办法。那就是shared_ptr这个东东,没想到太久没有紧跟C语言的发展,都不知道C++11里面有个这么好用的玩意,真是帮了我的大忙。
度娘:shared_ptr是一种智能指针(smart pointer)。shared_ptr的作用有如同指针,但会记录有多少个shared_ptrs共同指向一个对象。这便是所谓的引用计数(reference counting)。一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。这在非环形数据结构中防止资源泄露很有帮助。
    看这解释,真是为我所遇到的这个问题量身定做的解决方案啊,堪称完美解决了,废话不说,直接拿来用起:

点击(此处)折叠或打开

  1. #include <memory>

  2. using namespace std;

  3. typedef std::shared_ptr<double> MatMemSP;

  4. class CMatrix
  5. {
  6. public:
  7.     UINT r;
  8.     UINT c;
  9.     MatMemSP sp_e;
  10.     double* e;
  11. ...
  12. }
    要用到shared_ptr,就必须包含头文件,并使用std命名空间。完了shared_ptr用模板<>来定义内存区间的指针类型,因为其本身是封装的一个类。我这里的内存区间是定义成double*的指针,所以使用shared_ptr的时候就采用来定义。在构造函数和析构函数里面就通过shared_ptr类型的成员变量sp_e来对分配的内存区进行管理:

点击(此处)折叠或打开

  1. CMatrix::CMatrix(UINT row, UINT col)
  2. {
  3.     ...
  4.     sp_e = MatMemSP(new double[r*c]);
  5.     e = sp_e.get();
  6.     ...
  7. }

  8. CMatrix::~CMatrix()
  9. {
  10.     ...
  11.     sp_e.reset();
  12.     e = NULL;
  13.     ...
  14. }
    在构造函数里面,将new double[r*c]分配的内存区赋给sp_e,让sp_e实现对这块内存区的管理。再让e指向刚刚分配的内存区,以便于其他成员函数用double指针进行使用。在析构函数里面通过调用sp_e.reset()对内存区进行释放,shared_ptr自己会对占用内存的对象进行计数,每次有对象调用reset()进行释放的时候就将计数减1,当减到0的时候才会真正释放内存区,这样就不会出现重复释放的情况了。
    赶快编译,运行!走起,完美解决!




阅读(1601) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~