深拷贝和浅拷贝

起因

最近原公司老板和一家公司合作开了个新公司,派遣了包括我在内的四位程序猿过去做开发,从原公司离职又到新公司报到,结果这刚过一个月就有一位离职又回原公司了,可能是新公司从零开始代码量太大,他不适应的原因吧

言归正传,在新公司我们在做5G设备,毕竟现在5G比较火,我之前在原公司使用的是高通X55平台,没错就是iphone12使用的基带芯片,突出一个贵字,所以销量惨淡,一直在各种送样送客户,所以新公司直接上国内展讯平台,就是要便宜,完全从零开发实在是痛苦,所以在征得原公司领导的同意后,决定使用我之前部门的base库,这个库包含了C/S通讯,数据库操作,xml操作,上报时间,观察者模式等常用的基类和功能类,在使用这个库的时候出现了double free导致程序dump,一个在高通平台使用了两年的库,为什么拿到展讯平台就出问题呢?

分析错误原因

经过gdb 调试定位到了出问题的类,由于版权原因我简单的表达一下这个类及base库中的使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class data_uri
{
private:
char *m_uri;

public:
explicit data_uri(const char *uri)
{
int uri_length = 0;

uri_length = strlen(uri);
m_uri = new char[uri_length + 1];
strcpy(m_uri, uri);
};
virtual ~data_uri()
{
if (m_uri != NULL)
{
delete[] m_uri;
}
};
};

int main()
{
const char uri[128] = "db:teacher#limei";
data_uri m_data_uri = data_uri(uri);
}

这个类在构造函数中分配了内存,但是没有定义拷贝构造函数,也没有重写运算描述符,这时会走默认构造函数,只是简单的把指针拷贝过去,即等号右边的 data_uri(uri) 先调用构造函数,然后调用拷贝构造函数,将类拷贝给 m_data_uri,注意两边的类中m_uri指向的是同一片内存,这时等号右边没有用了,会调用析构函数,由于左右两边指向同一块内存,所以当函数运行结束,左边再次析构的时候就会double free

解决方法:

  1. 写一个拷贝构造函数
  2. 直接使用数组,这样不用再写拷贝构造函数,坏处是不能动态分配内存比较浪费空间

深拷贝和浅拷贝

关于这个问题,大多讲的很复杂,我这里说下我的个人见解

  1. 深拷贝和浅拷贝是对于类中有指向堆内存的指针时,不同的拷贝行为的描述
  2. 浅拷贝指简单的赋值,没有新内存的分配,双方指向同一块内存
  3. 深拷贝指开辟新的内存,将前者内存中的内容拷贝到自己的内存中

这样应该就很好理解上面为什么会出错了,如果不写拷贝构造函数,默认拷贝构造函数可以完成对象的数据成员简单的复制,对象的数据资源是由指针指向的堆时,默认的拷贝构造函数只是将指针复制

为什么之前不出错

这个问题就有意思了,为什么这个很容易发现的错误,却在项目中使用了两年呢?经过查看编译过程,发现在高通平台的交叉编译器会优化编译。编译器实现省略创建一个只是为了初始化另一个同类型对象的临时对象,即编译器认为不需要先构造再拷贝再析构,太麻烦了完全没必要,所以就把右边的操作全部优化掉了,直接构造一次就可以了

展讯平台设置了编译选项:-fno-elide-constructors,这个选项将关闭这种优化,强制编译器在所有情况下都调用拷贝构造函数,man手册描述如下:

The C++ standard allows an implementation to omit creating a temporary that is only used to initialize another object of the same type. Specifying this option disables that optimization, and forces G++ to call the copy constructor in all cases.


深拷贝和浅拷贝
https://carl-5535.github.io/2021/09/23/工作总结/深拷贝和浅拷贝/
作者
Carl Chen
发布于
2021年9月23日
许可协议