一次析构函数引发的错误

🎐在处理向运输层添加数据报时, 定义了一个结构体, 结构体中包含一个byte类型的指针data, 用来存储数据存放的地址, 另外还有一个uint32_t类型的size用来存放数据占用的字节数, 结构体按照8字节对齐, 无论如何调整size和data两个成员的位置, 也都有4个字节无法使用. 这4个字节用不了, 重新定义size的类型为size_t, 为了方便拼接协议的各个部分, 重载了结构体的+运算.

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
typedef struct Data {
size_t size;
byte *data;

Data() {
size = 0;
data = nullptr;
}

~Data() {
printf("free! \n");
if (size > 0 && data != nullptr) {
delete [] data;
data = nullptr;
size = 0;
}
}

struct Data operator+(struct Data & other) {
struct Data result;
result.size = size + other.size;
result.data = new byte[result.size];
memcpy(result.data, data, size);
memcpy(result.data + size, other.data, other.size);
return result;
}

} Data;

结构体里面的data存储的是堆中存放数据的地址, 重载+运算符会生成一个新的对象, 这就涉及到内存管理了, 如果执行a + b + c这样的运算, 其中相加过程中会生成两个结构体变量, 至少有一个属于临时变量, 运算结束后就会释放, 因此必须在析构函数中释放data所指向区域的内存. 上面的代码看上去没有问题.

尝试运行下面👇的代码:

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
Data test(Data & a) {
Data b = a;
Data c = a + a;
Data d = c + a;
Data e = b + d;
return e;
}

int main(int argc, const char * argv[]) {
// insert code here...
Data m;
m.size = 3;
m.data = new byte[3];

Data n;
n.size = 4;
n.data = new byte[4];

Data l = m + n;

Data t = test(l);

sleep(10);

return 0;
}

程序会报一个错误

1
2
malloc: *** error for object 0x10072d520: pointer being freed was not allocated
malloc: *** set a breakpoint in malloc_error_break to debug

大致的意识就是free释放的区域不是malloc申请的, 在这个例子中也就是delete了不属于new申请的区域. 仔细检查了一下代码, 并没有这种情况. 那为什么会出现这种情况呢? 添加断点后发现, 对同一个地址调用了两次delete, 所以自然会报这种错误. 在test函数中, 临时变量b离开作用域以后调用了一次析构函数, 这里b被赋值成了a, test函数中声明的参数是左值引用, test调用前没有拷贝参数到形参, 这里没问题, 问题在于对b复制时, 可能只是浅拷贝, 也就是只对对应的成员变量赋值, 两个data指向同一个区域. 因此需要重写拷贝函数和拷贝构造函数, 拷贝构造函数一般是变量声明的同时赋值使用, 拷贝函数则是拷贝到已有变量. 拷贝函数般重载运算符=, 拷贝构造函数有两种常见的形式:

1
2
3
4
5
6
Data b;
// 拷贝构造
/// 形式1
Data a = b;
/// 形式
Data a(b);

添加拷贝构造函数

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
typedef struct Data {
size_t size;
byte *data;

Data() {
size = 0;
data = nullptr;
}

~Data() {
printf("free! \n");
if (size > 0 && data != nullptr) {
delete [] data;
data = nullptr;
size = 0;
}
}

Data(struct Data & other) {
if (other.size > 0 && other.data != nullptr) {
size = other.size;
data = new byte[size];
memcpy(data, other.data, size);
} else {
size = 0;
data = nullptr;
}
}

struct Data & operator=(struct Data & other) {
if (this == &other) {
return *this;
}

if (other.size != 0 && other.data != nullptr) {
if (size != 0 && data != nullptr) {
delete [] data;
}
size = other.size;
data = new byte[size];
memcpy(data, other.data, size);
}

return *this;
}

struct Data operator+(struct Data & other) {
struct Data result;
result.size = size + other.size;
result.data = new byte[result.size];
memcpy(result.data, data, size);
memcpy(result.data + size, other.data, other.size);
return result;
}

} Data;

利用Xcode提供的工具检查一下有没有内存泄漏

Xcode菜单

打开菜单栏Product->Profile即可. 快捷键是 + I

内存泄漏

内存泄漏检测窗口

开始检查