0%

认识C++内存管理的工具 (二)

7. 构造和析构的直接调用方式

通过指针不可以直接调用构造函数,但可以直接调用析构函数。可以通过placement new来直接调用构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct A
{
int a;
int b;
A(int _a, int _b) : a(_a), b(_b) {}
};

int main()
{
A* tmp_a = new A(2, 8);
// tmp->A::A(2, 8); // error! cannot call constructor 'A::A' directly
tmp_a->~A(); // 可以的

// 虽然调用了析构函数, 但事实上 tmp_a 的空间并没有释放
// 因为没有 free 掉, 那块内存还在, 值没有被重写
// 所以可以访问 通过 tmp_a 访问原对象的值
// 如果是 delete tmp_a; 就不能访问
// 因为 delete 调用完析构函数之后, 还调用了 free 函数释放那块内存

cout << tmp_a->a << endl; // 所有这里访问的值看似没有变化
}

8. array new/delete

new []/delete[]本质调用malloc/freemalloc/free细节:

  • 除了分配给定的内存之外,还会有cookie——用来记录分配数组的长度,查看cookie中的长度,然后调用适当次数的析构函数

  • 所谓内存泄漏

    对于一个new [],需要有相应的delete [],本质上cookie记录了free本身需要释放的内存大小,泄露则发生在调用的析构函数次数上:如果对象含有指针的data member,指向堆上的内存,则意味着析构函数是nontrival,需要再析构函数中释放指针指向的内存,反之则是trival的,而析构函数的调用次数,则会影响指针指向的内存部分的泄露。

image-20210316200134379

image-20210316201727370

image-20210316201922911

9. placement new

标准不允许直接用指针调用构造函数,但给出了另一种调用语法,可以在现有的对象的内存中调用构造函数,它不会分配新的内存。这也是没有对应的placement delete的原因。

image-20210316202736446

10. C++程序分配内存的途径

image-20210316203737564

image-20210316205136096

从图中可以看出,当出现new Foo(x)后,编译器会检查Foo这个类有没有实现operator new(size_t)static函数,如果有就会调用Foo这个版本的operator new,如果没有就调用全局的::operator newdelete也是如此。

所以可以为一个类单独实现operator newoperator delete。也可以重载全局的operator new/delete,但很少这么做,因为全局的版本是照顾所有的类。

11. 探究operator new

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
A* a = new A();
new (a) A(27, 224);

void* b = ::operator new(sizeof(A));
cout << b << endl;

cout << a->a << endl;
cout << a->b << endl;
}

// 汇编代码如下:

12. 重载::operator new/delete

上面说了,重载全局的operator new/delete影响深远,谨慎使用。但可以重载,方法是在namespace中声明和全局版本相同的函数签名。

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
using namespace std;
struct A
{
int a;
int b;
A()
{
cout << "A() : this:" << this << endl;
}
A(int _a, int _b) : a(_a), b(_b)
{
cout << "A(int,int): this: " << this << endl;
}
~A()
{
cout << "~A(): this: " << this << endl;
}
};

// 以下 4 个函数就在当前的文件中声明定义
inline void* operator new(size_t len)
{
return malloc(len);
}

inline void* operator new[](size_t len)
{
return malloc(len);
}

inline void operator delete(void* ptr)
{
return free(ptr);
}

inline void operator delete[](void* ptr)
{
return free(ptr);
}

int main()
{
A* a = new A(); // 调用到上面自定义的 operator new
void* b = ::operator new(sizeof(A)); // 虽然指定的是 ::operator new, 但还是调用到上面自定义的版本

// 对于 a 是正确的释放, 先调用 A 的析构函数, 再调用上面的 operator delete
delete a;

// 对于 b, 释放的操作应该显示调用 operator delete
// 这里用 delete 后, 并没有调用 A 的析构函数, 而是直接调用了 operator delete
// 可见编译器对于 delete void* 的时候, 是直接转换为 opreator delete
delete b;
}