一个C++bug引入的许多知识
一、前言
假设我们有一个Car类,用了表示一个车,它有id,名字,牌照等许多东西,还有一个表示车的部件CarPart。
但出于某方面的考虑,我们不打算在产生car这个对象的时候,就生产出这个车,你可以认为这个时候,只有一个纸糊的车摆在你的面前,它有id,有名字,有牌照,但是它不能动,只有我们打算启动这个车的时候,才去给这个车配置发动机,轮胎等各个部件。
二、错误代码1
//CarPart类 用了标识车内的各个部件

//Car类 用了标识车

我们定义了一个car类,它里面有一个_id标识这个car,也有一个_car来标识这个车的各个部件,在最开始的时候,_car指针是null,当我们调用getCar的时候,我们判断这个车是否创建好了部件,有的话就返回部件,没有的话,为这个车创建部件,至于具体的创建步骤,也许是在工厂制造,也许是从其他地方抢来的也有可能,然后返回车的部件
main函数

我们在一个循环里来创建car对象,创建这个车的部件,并把这个对象放进一个vector里,在这个循环里,我们只会循环一次,至于原因你在下面会看到
然后我们运行程序,刚开始看起来很正常,但是糟糕…程序出现了问题
g++ -g main.cpp -o main.out //(使用-g选项来生成调试文件)
./main.out
Start
Make 4 tires of car 0
Make engine of car 0
-------------------
End
*** glibc detected *** double free or corruption (fasttop): 0x0000000000503010 ***
我们看到程序出现了一些问题,产生了一个core文件
我们用gdb查看一下这个core文件
gdb main.out
(gdb) core-file core.45393
(gdb) bt
#0 0x0000003f0b02e2ed in raise () from /lib64/tls/libc.so.6
#1 0x0000003f0b02fa3e in abort () from /lib64/tls/libc.so.6
#2 0x0000003f0b062d41 in __libc_message () from /lib64/tls/libc.so.6
#3 0x0000003f0b06881e in _int_free () from /lib64/tls/libc.so.6
#4 0x0000003f0b068b66 in free () from /lib64/tls/libc.so.6
#5 0x000000342cfae19e in operator delete () from /usr/lib64/libstdc++.so.6
#6 0x0000000000400dc0 in ~Car (this=0x503030) at Car.h:29
#7 0x00000000004016fd in std::_Destroy<Car> (__pointer=0x503030) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:107
#8 0x000000000040155b in std::__destroy_aux<Car*> (__first=0x503030, __last=0x503040) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:120
#9 0x0000000000401103 in std::_Destroy<Car*> (__first=0x503030, __last=0x503040) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:152
#10 0x0000000000400f89 in ~vector (this=0x7fff0f7371a0) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_vector.h:256
#11 0x0000000000400d0a in main () at main.cpp:17
我们看到程序是从程序的第17行结束,调用析构函数时出现的问题
析构函数出错的原因一般是多次释放同一块内存
那么这里的问题出现在那里呢?
我们想一想第12行我们创建了一个temp对象,然后第13行为这个temp对象创建了汽车组件
这个时候的内存看起来是这个样子

接着我们把temp放进了vector中,这个时候会调用car的拷贝构造函数,由于car没有定义自己的拷贝构造函数,因此将会执行默认的拷贝构造函数进行浅拷贝操作
这个时候的内存是这个样子

当第一次循环结束的时候,temp被析构,汽车组件被delete掉
然后当程序结束的时候,对vcar[0]进行析构,由于Temp中的_car和Vcar[0]中的_car对象指向了同一块内容,vcar[0]所指的汽车组件已经被释放掉,再次delete的时候,造成错误
三、错误代码2
我们刚刚看了一个版本的错误代码,现在我们来看看另一个版本的错误代码
CarPart和Car类和上一个版本的一样
main函数有所不同

这里我们没有直接操作temp对象,而是通过vcar.back()获取刚刚push_back进去的对象,并在它上面进行getCar操作,这样就避免了temp和vcar[0]中的指针指向同一块内存
我们运行程序,看起来一切正常
Start
Make 4 tires of car 0
Make engine of car 0
-------------------
End
然后,我们把第10行 稍作一下修改,让它循环2次,再次运行,该死,程序又出错了
Start
Make 4 tires of car 0
Make engine of car 0
-------------------
Make 4 tires of car 1
Make engine of car 1
-------------------
End
*** glibc detected *** double free or corruption (fasttop): 0x0000000000503030 ***
查看core文件,发现又是在析构函数处出现了问题
(gdb) bt
#0 0x0000003f0b02e2ed in raise () from /lib64/tls/libc.so.6
#1 0x0000003f0b02fa3e in abort () from /lib64/tls/libc.so.6
#2 0x0000003f0b062d41 in __libc_message () from /lib64/tls/libc.so.6
#3 0x0000003f0b06881e in _int_free () from /lib64/tls/libc.so.6
#4 0x0000003f0b068b66 in free () from /lib64/tls/libc.so.6
#5 0x000000342cfae19e in operator delete () from /usr/lib64/libstdc++.so.6
#6 0x0000000000400f80 in ~Car (this=0x504080) at Car.h:42
#7 0x0000000000401b65 in std::_Destroy<Car> (__pointer=0x504080) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:107
#8 0x00000000004019d5 in std::__destroy_aux<Car*> (__first=0x504080, __last=0x5040a0) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:120
#9 0x0000000000401411 in std::_Destroy<Car*> (__first=0x504060, __last=0x5040a0) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_construct.h:152
#10 0x0000000000401259 in ~vector (this=0x7fff3ead6110) at /usr/lib/gcc/x86_64-redhat-linux/3.4.5/../../../../include/c++/3.4.5/bits/stl_vector.h:256
#11 0x0000000000400ea2 in main () at main.cpp:18
(gdb)
为什么把循环从一次改成两次就会出错了呢
我们进如果打印vcar里对象中_car的地址,会发现他们竟然是一样的


那么这又是为什么呢
在C++中,堆内存是存在复用的可能的,如果上一个内存已经被释放调,在new新对象的时候,新对象的内存便可能建立在刚刚释放的内存上
我们知道vector内部是类似数组的连续的储存空间
vector在发现空间不足时,会在其他地方重新申请一块内存空间,调用原来对象的拷贝构造函数 在新的地方进行创建,并把原来地方的对象析构调
第一次循环的时候 vector的大小是1,容量也是1,在第二次调用,由于这个时候,放进了第二个元素,所以vector的大小需要进行调整,便在新的地方重新申请了一块内存,调用了car的拷贝构造函数,并将原来的对象进行析构,所以导致了第二次创建的对象的_car地址和第一个对象一样
这样当程序结束调用析构函数的时候,由于vcar[0]和vcar[1]中_car指向同一块内存,在delete时就会出现问题
问题的根源依旧是没有深拷贝构造函数
四、结论
1、赋值函数,拷贝构造函数,析构函数通常应该被视为一个整体,即需要析构函数的类也需要赋值函数和拷贝构造函数,反之亦然
2、为了支持快速访问,vector将元素连续储存,当不得不获取新的内存空间的时候,vector会其他地方申请新的空间,并将元素从旧的地方移动到新的地方,这期间会调用元素的析构函数和拷贝构造函数
3、C++中堆内存是可以复用的,当你释放一块内存之后,又立即申请一块内存,新申请的内存空间很可能在刚刚释放的内存上
一个C++bug引入的许多知识的更多相关文章
- 从一个小Bug,到Azure DevOps
1. 一个小Bug 最近和同事提起一个几年前的 Bug,那是一个很小很小的 Bug,没什么技术含量.那时候我刚入职,正好公司卖了一款仪器到某个国家,但是那边说配套的软件运行不起来,一打开就报错.经过排 ...
- 如何从头到脚彻底解决一个MySQL Bug
摘要:为了保障华为云GaussDB产品的可靠性,每一款产品发布前都要通过多轮严苛的测试用例. 说明:本文中的MySQL,如果不做特殊说明,指的是开源社区版MySQL. 华为云数据库新版本在发布之前,会 ...
- VS编译器优化诱发一个的Bug
VS编译器优化诱发一个的Bug Bug的背景 我正在把某个C++下的驱动程序移植到C下,前几天发生了一个比较诡异的问题. 驱动程序有一个bug,但是这个bug只能 Win32 Release 版本下的 ...
- maven引入jar包时,一个jar的引入错误,会导致后来的jar包的引入。
maven引入本jar包时,引入失败. 问题是另一个jar没有引入正确.
- 关于一个小bug的修正
python初学者,非常喜欢虫师的文章. 练习时发现一个小bug,http://www.cnblogs.com/fnng/p/3782515.html 验证邮箱格式一题中,第三个x不允许有数字,但是测 ...
- 最近调试HEVC中码率控制, 发现HM里面一个重大bug
最近调试HEVC中码率控制, 发现里面一个重大bug! 码率控制中有这么一个函数: Int TEncRCGOP::xEstGOPTargetBits( TEncRCSeq* encRCSeq, Int ...
- 记一个逻辑bug
1 从数据库中找出一个学生能选的毕业设计(毕设的select or not 字段表示本题目是否已经被选 此时就按照其值为n来查询) 2 用户选择某个毕设后,先更新毕设表(select ...
- Lazarus下面的javascript绑定另外一个版本bug修正
Lazarus下面的javascript绑定另外一个版本bug修正 从svn 检出的代码有几个问题 1.fpcjs.pas 单元开始有 {$IFDEF FPC} {$MODE delphi} {$EN ...
- 在mysql中RIGHT JOIN与group by一起使用引起的一个大bug
本来按理说这个小问题不值得写一个博客的,不过正是这个小问题造成了一个大bug. 本来每月对数据都好好的,但是这一两天突然发现许多数据明显不对,这一块的代码和sql有些不是我写的,不过出现了bug,还是 ...
随机推荐
- linux下使用shell脚本获取终端宽度
获取终端大小时候的学习 学习日期:2018/11/3 问题来源: 在写shell脚本时想输出一行占满整个终端屏幕宽度的 横杠 发现for循环会导致执行缓慢 解决方法: 使用yes 命令 sed '50 ...
- React Webpack cookbook
https://christianalfoni.github.io/react-webpack-cookbook/index.html https://fakefish.github.io/react ...
- 20155235 《网络攻防》 实验一 逆向及Bof基础实践说明
20155235 <网络攻防> 实验一 逆向及Bof基础实践说明 实验目的 本次实践的对象是一个名为pwn1的linux可执行文件. 该程序正常执行流程是:main调用foo函数,foo函 ...
- 【转载】特殊宏://{{AFX_MSG、//{{AFX_VIRTUAL、//{{AFX_MSG_MAP、//{{AFX_DATA_INIT
原文:http://yyguangzheng.blog.163.com/blog/static/603488402014102215153610/ 说明:这篇日志我不知道怎么命名好,虽然内容很少,但是 ...
- 使用终端命令行将本地项目上传到Github
使用终端命令行将本地项目上传到Github 转自https://blog.csdn.net/fishball1/article/details/52020305 对于IOS开发者来说,Github的使 ...
- Currency Exchange POJ - 1860 (spfa)
题目链接:Currency Exchange 题意: 钱的种类为N,M条命令,拥有种类为S这类钱的数目为V,命令为将a换成b,剩下的四个数为a对b的汇率和a换成b的税,b对a的汇率和b换成a的税,公式 ...
- jsp手动分页
注意: sql语句要写对,jsp显示 List 时的 item的字段名要写对 这里 where uid 要放在前面才能成功执行,否则会报错 , 在写items的时候,如果controller里面已经写 ...
- mysql索引原理及查询速度优化
一 介绍 为何要有索引? 一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,在生产环境中,我们遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,因此对查询语句 ...
- 如何创建XHTML表单
首先奉上本菜曾经的作业——一张模仿智联招聘中的注册表单.虽然没有加样式显得很难看,但表单的基本要素已经具备了. 接下来进入正文,开始介绍各表单元素 form标签: 在创建表单时,第一步就是form标签 ...
- 《数据结构与算法图解》 分享 pdf下载
链接:https://pan.baidu.com/s/1gOMlwU5ucHYDVazvVMk2uw提取码:bk5x