使用memmove/memcpy库函数拷贝内存时容易引发的异常

  1. 首先,我们来看一下C库函数memmove的原型,如下:
    void memmove( void dest, const void* src, size_t n);
    头文件:<string.h>
    功能:由src所指内存区域复制n个字节到dest所指内存区域。
    返回值:函数返回指向dest的指针。

  2. 其次,C库函数memcpy的原型如下:
    void *memcpy(void *dest, const void *src, size_t n);
    头文件:<string.h>
    功能:由src所指内存区域复制n个字节到dest所指内存区域。
    返回值:函数返回指向dest的指针。

  3. 然后,简单描述一下上述两个库函数的区别:
    当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的,memcpy不保证拷贝的结果的正确。
    <1> 内存不重叠情况:
    <2> 内存重叠的情况之一:
    <3> 内存重叠的情况之二:
    <4> 对于内存重叠两种情况,memmove和memcpy拷贝内存情况如下:


    红色字体表示原源地址内存存储的数据内容,绿色字体表示原目的地址内存存储的数据内容,紫色字体代表用memmove函数内存拷贝后目的地址存储的正确结果,蓝色字体表示用memcpy函数内存拷贝后目的地址存储的有可能错误的结果。

  4. 言归正传,下面我们来探讨一下使用memmove/memcpy库函数拷贝内存时容易引发的异常

<1> 首先,先看下面的一段简单的代码:

#include <iostream>
#include <string.h>class Base {public:int base_id = 0;std::string base_name;float * depths = nullptr;Base() {}virtual ~Base() {delete[] depths;depths = nullptr;}int GetBaseId() { return base_id; }std::string GetBaseName() { return base_name; }float * GetDepths() { return depths; }virtual void ChangeBaseName() { base_name = "base name"; }
};class BaseInfo : public Base {public:int baseinfo_id = 0;std::string baseinfo_name;Base() {}~Base() {}
};int main() {BaseInfo * binfo1 = new BaseInfo[2];binfo1[0].baseinfo_id = 1;binfo1[0].baseinfo_name = "baseinfo 1";binfo1[0].depths = new float[3] {0.4f, 0.7f, 0.3f};binfo1[1].baseinfo_id = 2;binfo1[1].baseinfo_name = "baseinfo 2"; binfo1[1].depths = new float[2] {0.9f, 0.7f};BaseInfo * binfo2 = new BaseInfo[2];memmove(binfo2, binfo1, 2 * sizeof(class BaseInfo));delete[] binfo1;binfo1 = nullptr;delete[] binfo2;binfo2 = nullptr;return 0;
}

<2> 调试上面的这段代码,当程序运行到“delete[] binfo2;”这一行时,程序中断发生错误,抛出异常如下:
<3> 那么是什么原因引发了异常呢?

1" memmove函数是用来拷贝内存的,我首先想到的是【src源地址内存和dest目的地址的内存有重叠,导致重复释放同一块内存区域产生异常】,经过添加代码,验证两块内存并没有重叠部分,遂排除此种可能性。

2" 其次我想到的是【BaseInfo里的成员变量包含指针,两个BaseInfo对象的指针成员指向同一块内存区域,析构对象时导致重复释放同一块内存区域产生异常】。
回到代码中可以看出BaseInfo从基类Base继承了一个指针变量“depths”,看下图:depths(3)/depth(4)是通过拷贝了depths(1)/depths(2)的内存得来的,因此depths(1)的值等于depths(3)的值,即这两个数组指针指向内存中同一块内存区域(同理,depths(2)的值等于depths(4)的值。所以当我们delete[] baseinfo1时调用析构函数时第一次释放了depths(1)所指向的内存,再delete[] baseinfo2时调用析构函数时会第二次去释放depths(3)所指向的内存,重复释放内存导致异常。

3" 将成员depths注释掉以及其相关的接口改掉再编译调试代码发现还是会有同样的异常,我想到的是【会不会是因为派生类继承了基类的virtual虚函数,派生类的第一个成员是虚表,派生类对象的成员包含虚指针,在释放虚指针的时候导致重复释放而造成异常呢?】,后面经过验证发现类里面包含虚函数不会引发这个异常,稍后第5点讨论。

4" 引发异常的另一个原因有时候很难想到,原因是【BaseInfo的成员中包含std::string类型的变量】。我们下面来看一下string的原型,它用来表示字符串,但实质上它是一个类。string中含有一个m_data的指针变量,所以也会导致重复释放同一块内存的异常,原理同2"相似。

class String
{public:String(const char *str = NULL); // 普通构造 String(const String &other); // 拷贝构造函数~ String(void); // 析构函数String & operate =(const String &other); // 赋值函数
private:char *m_data; // 用于保存字符串
};

5" 最后关于【虚指针的释放】:
一个存在虚函数的类会有一个虚表,这个类的对象都包含一个成员即虚指针,这些虚指针都指向这个类的虚表,即同类的对象共享这个类的虚表。虚表相当于一个一维数组,它里面按顺序存放这个类里每个虚函数实现的地址。在构造函数中进行虚表的创建以及虚指针的初始化,而虚指针的释放我也没太弄清楚,不知道它是在什么时候如何释放的。
我的猜想是:
在构造A类的第一个对象时构造了这个类的虚表,并将这个对象的虚指针初始化为这个虚表,之后再构造A类对象时将它们的虚指针都初始化为此虚表,所有A类对象共享A类的虚表;在析构A类的对象时只是将虚指针置空,直到析构最后一个A类的对象时才将它的虚指针指向的虚表从内存中释放掉,所以上述的程序才没有导致重复释放内存的异常。(貌似有点道理,好像又有点牵强,哈哈)

我是个小神女,快来关注我吧~

使用memmove/memcpy库函数拷贝内存时容易产生的异常相关推荐

  1. memmove() -- 拷贝内存内容

    为什么80%的码农都做不了架构师?>>>    memmove() -- 拷贝内存内容 2007年07月06日 星期五 11:41 相关函数: bcopy(), memccpy(), ...

  2. memcpy和memmove的区别以及内存重叠问题

    memcpy和memmove的区别以及内存重叠问题 转自:https://www.codecomeon.com/posts/89/ 区别 memcpy() 和 memmove() 都是C语言中的库函数 ...

  3. [经典面试题]实现memcpy库函数

    [题目] 已知memcpy的函数为: void* memcpy(void *dst , const void* src , size_t count) 其中dst是目的指针,src是源指针.不调用c+ ...

  4. CUDA Samples: dot product(使用零拷贝内存)

    以下CUDA sample是分别用C++和CUDA实现的点积运算code,CUDA包括普通实现和采用零拷贝内存实现两种,并对其中使用到的CUDA函数进行了解说,code参考了<GPU高性能编程C ...

  5. 字符操作库函数以及内存操作库函数 C语言实现

    字符操作库函数 strlen strlen判断结束的标志为找到字符串中的'\0',也就是说如果字符串中间出现'\0'将会导致strlen停止 即strlen的返回值是'\0'前所出现的字符个数   然 ...

  6. CUDA零拷贝内存(zerocopy memory)

    为了实现CPU与GPU内存的共享,cuda采用了零拷贝内存,它值固定内存的一种,当然,也就是实际存储空间实在cpu上. 零拷贝内存的延迟高,在进行频繁的读写操作时尽量少用,否则会大大降低性能. /** ...

  7. Python在计算内存时值得注意的几个问题

    作者 | 豌豆花下猫 来源 | python猫(ID:python_cat) 我之前的一篇文章,带大家揭晓了 Python 在给内置对象分配内存时的 5 个奇怪而有趣的小秘密.文中使用了sys.get ...

  8. 当我们在谈论内存时,我们在谈论什么

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"加群"获取公众号专属群聊入口 来源:阿里巴巴中间件 内存,是程序员绕不过的一道坎.写过 ...

  9. c python 内存冲突_Python在计算内存时应该注意的问题?

    我之前的一篇文章,带大家揭晓了 Python 在给内置对象分配内存时的 5 个奇怪而有趣的小秘密.文中使用了sys.getsizeof()来计算内存,但是用这个方法计算时,可能会出现意料不到的问题. ...

最新文章

  1. Oracle 并行原理与示例总结
  2. js小记 function 的 length 属性
  3. Yii2.X 多语言-类图
  4. RequireBusyDialog
  5. 检测字符串包含emoji表情
  6. Google上面关于cas的文章
  7. [react] 为什么属性使用className而不是class呢?
  8. LSTM 与 Bilstm介绍(包含代码实现、Python)
  9. oracle登录无法处理服务名,ORA-12154: TNS: 无法处理服务名 plsql能登陆
  10. mt4交易软件云服务器_MT4软件使用教程1常见货币对交易图表类型
  11. bzoj 1046: [HAOI2007]上升序列
  12. 在CentOS 6.7部署wordpress博客系统Discuz论坛系统
  13. java生成点阵图_Android从SD卡读取图片并显示为点阵图
  14. 杭电计算机17年复试真题详解
  15. Flutter音频播放插件just_audio入门指南
  16. bch matlab,求助!关于matlab中BCH码的弱问题
  17. app提示已到期_安装软件时,显示软件证书过期,怎么回事?
  18. RFID射频识别系统简述
  19. 怎么提高文公写作水平?公文写作礼仪类模板(1)
  20. php实现鼠标悬停显示下拉菜单,jquery实现鼠标滑过显示二级下拉菜单效果

热门文章

  1. 研究生英文论文练习互改——Peer feedback 和 Peer grading 的访谈思考
  2. Windows下搭建免费个人博客
  3. 关于分布式事务、两阶段提交、一阶段提交、Best Efforts 1PC模式和事务补偿机制的研究[转]
  4. 流利说硅谷AI Lab负责人刘扬:语言学习产品如何在技术上保持优势?
  5. SuperMap iClient3D for WebGL 用 WebMapTileServiceImageryProvider 接口加载天地图 WMTS 服务
  6. java 有序数组 频率_java基础最全面总结_1
  7. 注册表:HKCR, HKCU, HKLM, HKU, HKCC,注册表中常用的5种数据类型
  8. 小坤二次元导航HTML源码 很好看的引导页
  9. 中学教师计算机运用培训简报,第十中学“教育信息化能力提升”培训活动简报...
  10. 万物皆对象,Python的对象概述(简述)