最近做项目, 有个地方是外包人员写的, 其中有个函数,大致这样

void getInfo(std::shared_ptr<Info>& outInfo); 这个函数是一个dll(链接静态vc库, 使用/MT链接选项)。

我在exe(也是/MT选项)中使用这个函数, 一开始看了,感觉危险,为啥呢?因为我是这样调用的

f()

{

std::shared_ptr<Info> tmpInfo;

getInfo(tmpInfo);

}

所以,内存是在dll里面分配的,但是 内存在f退出时,析构 tmpInfo, 肯定会有释放的动作,这岂不是 dll分配  , exe释放吗?而且还是静态链接, 必然会 堆冲突啊, 然而无论我怎么测试 , 就是好的出奇, 没有任何挂掉的迹象。 我想了好几天, 也跟了好几次std::shared_ptr的代码, 明明最后看到有 delete p这样的类似语句, 那么delete不是释放了不该管的内存吗?

今天又跟踪, 突然发现一个诡异的问题,发现在f退出前, tmpInfo析构时竟然 代码最后进入到了dll里面进行内存释放的调用,大致就是调用了一个函数  _destroy()的。 我跟了几次, 中间停下来想了各种可能的原因, 后来看到了reset时的一个 调用

shared_ptr(px这是那个分配的指针).swap(*this), 就是相当于 shared_ptr(px).swap(tmpInfo), 于是我就观察 ,跟踪, 发现在shared_ptr(px)临时对象析构时, 竟然进入了exe模块, 明明就是dll中reset内部的临时变量, 咋就跑到exe了呢? 后来发现 跳转时, 有个 call eax的地方, eax经过了几次mov的计算, 所以猜测应该是虚函数指针寻址进行调用的过程。

后来想了想, 觉得可能就是在swap时 , 临时的shared_ptr从(*this)中交换到了this对象初始化时所在模块的 delete地址, 所以才正确分配this相关的内存。

结合前几次看源代码的过程, 里面有几个对象定义了虚函数, 尤其是_destroy是虚函数, 而且使用了一个父类指针作为记忆的东西。 果然, 继续去看源码, shared_ptr有个 _Ref_cout_base*的成员变量,就是它记忆了相关分配时的信息。 他有个纯虚函数—_destroy,在 在 调用 reset时, 回调用 reset(px, new _Ref_count(px)),  这个 _Ref_count就是_Ref_cout_base的具体实现的子类,

这个子类又实现了接口_destroy(), 基本就是delete px的动作。 所以, 一切明了啦。 在reset时, 会同时调用一下类似的动作:

m_pRefCount = new _Ref_count(px); 这样m_pRefCount就跟调用shared_ptr的reset 时所在模块一致了, 所以, shared_ptr使用 临时对象和this对象的swap完成 reset函数的功能, 就可以保证, this对象的m_pRefCount对象所在模块就是reset调用时所在模块(我这里是dll中), 所以,当f 函数退出时, 会调用 m_pRefCount的_destroy函数, 而这个函数会通过虚函数表中记忆的m_pRefCount对象被初始化时所在模块中定义的该类的_destroy函数(虽然exe也有这个类的所有函数定义), 找到正确的_destroy地址, 而这个_destroy由于跟getInfo函数都是在dll定义的一个具体实现, 所以, 这个_destroy中所有的函数的直接调用(仅限于是直接调用(非虚函数的,有固定地址的),如果他也调用一个虚函数, 说不定通过虚函数表的寻址,又跑了其他模块去了,正如他自己就是这样跨模块的), 就是调用了dll里面的函数(包括delete), 所以, tmpInfo析构时, 调用tmpInfo.m_pRefCount的_destroy函数(虚的),会根据虚表指针走到dll函数里面真正的_destroy地址, 这样, 他所释放的也就跟他所包含的对象都在一个模块。这个关键还是 虚函数 的作用啦。

想一想, 如果m_pRefCount不是虚函数, 那么, 对象中就不会包含 他的成员函数地址, 就不会包含m_pRefCount地址(虚函数的地址间接存放在对象开头), 那么reset就交换不到外部模块函数地址, 当调用_destroy时, 就是调用了所在模块的_destroy, 就会调用所在模块的delete, 这样释放就会有问题。 所以, 正式因为虚函数的作用, 我们可以在对象本身中存放函数地址(固定的cpu寻函数地址指令就可以找到地址), 就可以记忆delete的地址(虽然delete很奇特的编译器实现的东西,语言上不可操控), 这样才可以保证对象的一致释放~~~~ 好啦, 回去睡觉啦, 还在公司呢, 刚吃个水饺, 欧耶~~~~~~

晓说智能指针shared_ptr为何可以实现跨模块分配和释放内存的更多相关文章

  1. 智能指针shared_ptr的用法

    为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Pointer). 智能指针的原理是,接受一个申请好的内存地址,构造一个保存在栈上的智能指针对象,当程序退出栈的作用域范围后,由于栈 ...

  2. c/c++ 智能指针 shared_ptr 和 new结合使用

    智能指针 shared_ptr 和 new结合使用 用make_shared函数初始化shared_ptr是最推荐的,但有的时候还是需要用new关键字来初始化shared_ptr. 一,先来个表格,唠 ...

  3. c/c++ 智能指针 shared_ptr 使用

    智能指针 shared_ptr 使用 上一篇智能指针是啥玩意,介绍了什么是智能指针. 这一篇简单说说如何使用智能指针. 一,智能指针分3类:今天只唠唠shared_ptr shared_ptr uni ...

  4. C++智能指针shared_ptr

    shared_ptr 这里有一个你在标准库中找不到的—引用数智能指针.大部分人都应当有过使用智能指针的经历,并且已经有很多关于引用数的文章.最重要的一个细节是引用数是如何被执行的—插入,意思是说你将引 ...

  5. 标准库中的智能指针shared_ptr

    智能指针的出现是为了能够更加方便的解决动态内存的管理问题.注:曾经记得有本书上说可以通过vector来实现动态分配的内存的自动管理,但是经过试验,在gcc4.8.5下是不行的.这个是容易理解的,vec ...

  6. C++ 智能指针 shared_ptr 分析

    引文: C++对指针的管理提供了两种解决问题的思路: 1.不允许多个对象管理一个指针 2.允许多个对象管理一个指针,但仅当管理这个指针的最后一个对象析构时才调用delete ps:这两种思路的共同点就 ...

  7. STL源码剖析-智能指针shared_ptr源码

    目录一. 引言二. 代码实现 2.1 模拟实现shared_ptr2.2 测试用例三. 潜在问题分析 你可能还需要了解模拟实现C++标准库中的auto_ptr一. 引言与auto_ptr大同小异,sh ...

  8. 智能指针 shared_ptr 解析

    近期正在进行<Effective C++>的第二遍阅读,书里面多个条款涉及到了shared_ptr智能指针,介绍的太分散,学习起来麻烦.写篇blog整理一下. LinJM   @HQU s ...

  9. 智能指针shared_ptr

    // 智能指针会自动释放所指向的对象. // shared_ptr的应用场景是:程序需要在多个对象间共享数据 /* 先从应用场景入手吧,说矿工A发现了一个金矿. * 然后矿工A喊来了矿工B,一起开采, ...

随机推荐

  1. 【JavaScript】关于JavaScript

    1. Language 2. DOM 3. Library 4. Framework

  2. poj 2892 &&hdu 1540 Tunnel Warfare

    http://poj.org/problem?id=2892 #include <cstdio> #include <cstring> #include <algorit ...

  3. 基于单例使用ThreadLocal对多线程下数据的访问修改

    package cn.lyy.thread; import java.util.Random; /** * 基于单例模式的基础上,使用ThreadLocal为每一个进入的线程生成一个实例, * 用来对 ...

  4. COJ 1006 树上操作

    传送门:http://oj.cnuschool.org.cn/oj/home/problem.htm?problemID=979 WZJ的数据结构(六) 难度级别:D: 运行时间限制:1000ms: ...

  5. g++ gcc 的区别

    我们在编译c/c++代码的时候,有人用gcc,有人用g++,于是各种说法都来了,譬如c代码用gcc,而c++代码用g++,或者说编译用gcc,链 接用g++,一时也不知哪个说法正确,如果再遇上个ext ...

  6. 大规模Hadoop集群在腾讯数据仓库TDW的实践

    随着业务的快速增长,TDW的节点数也在增加,对单个大规模Hadoop集群的需求也越来越强烈.TDW需要做单个大规模集群,主要是从数据共享.计算资源共享.减轻运营负担和成本等三个方面考虑. 数据共享.T ...

  7. oracle管道输出

    通常我们会在oracle中用dbms_output输出调试信息,但dbms_output只能在调用过程完成才返回结果,不能实时输出的.这意味着通常我们经常要等几分钟或更长的时间才能看到调试信息,那怎么 ...

  8. Android TabActivity之感叹

    (一)前言 在以前一篇帖子讲ams的时候,提了一下TabActivity.当时说它比较特殊就没有下文了,今天重发一篇帖子,跟大家探讨一下TabActivity. 做个假定先: 比如我们最外面的Acti ...

  9. PHP开发Android应用程序(转)

    第一部分是指在Android系统的手机上直接写PHP脚本代码并立即运行:第二部分则继续讲解如何把写好的PHP脚本代码打包成akp安装文件. 首先,在手机上安装两个apk包. 一个是SL4A(Scrip ...

  10. 子查询in和表连接效率

    在数据查询时,尽量减少in子查询而使用表连接的方式进行,效率更高.