【C++】—— 内存管理

9毫米的幻想 2024-08-27 10:05:02 阅读 60

【C++】—— 内存管理

1 C/C++ 的内存划分 1.1 C/C++ 的内存分布1.2 C/C++ 的内存分布练习

2 C语言 中动态内存管理方式:malloc/calloc/realloc/free3 C++ 内存管理方式3.1 new / delete 操作内置类型3.2 new 和 delete 操作自定义类型3.2.1 new 和 delete 操作自定义类型基础3.2.2 自动调用构造和析构的好处3.2.3 多参数自定义类型3.2.4 不存在默认构造3.2.5 空间开辟失败

4 operater new 与 operator delete 函数5 new 和 delete 的实现原理5.1 内置类型5.2 自定义类型

6 不匹配使用的情况6.1 new / free6.1.1 内置类型6.1.2 自定义类型

6.2 new [ ] / delete6.2.1 内置类型6.2.2 自定义类型

7 定位 new 表达式(placement-new)(了解)7.1 定位 new 用法7.2 定位 new 用途

8 malloc/free 和 new/delete 的区别

1 C/C++ 的内存划分

1.1 C/C++ 的内存分布

一个程序的数据存储是需要分区的,C/C++ 中常见的区域有:<code>栈、静态区(数据段)常量区(代码段)

在这里插入图片描述

说明:

<code>栈:又叫堆栈,非静态局部变量/函数参数/返回值等,栈是向下增长内存映射段:是高效的 I/O 映射方式,用于转载一个共享的动态内存库,用户可使用系统接口创建共享内存,做进程通信。:用于程序运行时动态内存分配,堆是向上增长数据段:存储全局数据静态数据代码段:可执行的代码/只读常量

1.2 C/C++ 的内存分布练习

我们来看下面一段代码及相关问题:

int globalVar = 1;

static int staticGlobalVar = 1;

void Test()

{

static int staticVar = 1;

int localVar = 1;

int num1[10] = { 1, 2, 3, 4 };

char char2[] = "abcd";

const char* pChar3 = "abcd";

int* ptr1 = (int*)malloc(sizeof(int) * 4);

int* ptr2 = (int*)calloc(4, sizeof(int));

int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);

free(ptr1);

free(ptr3);

}

题目:

选项:A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)

g

l

o

b

a

l

V

a

r

globalVar

globalVar 在哪里?______C

s

t

a

t

i

c

G

l

o

b

a

l

V

a

r

staticGlobalVar

staticGlobalVar 在哪里?______C

s

t

a

t

i

c

V

a

r

staticVar

staticVar 在哪里?______C

l

o

c

a

l

V

a

r

localVar

localVar 在哪里?______A

n

u

m

1

num1

num1 在哪里?______A

c

h

a

r

2

char2

char2 在哪里?______A

c

h

a

r

2

*char2

∗char2 在哪里?______A

p

C

h

a

r

3

pChar3

pChar3 在哪里?______A

p

C

h

a

r

3

*pChar3

∗pChar3 在哪里?______D

p

t

r

1

ptr1

ptr1 在哪里?______A

p

t

r

1

*ptr1

∗ptr1 在哪里?______B

答案详解:

globalVar全局变量,全局变量放在静态区CstaticGlobalVar 也是全局静态变量,放在静态区。与

g

l

o

b

a

l

V

a

r

globalVar

globalVar 的区别是:

s

t

a

t

i

c

G

l

o

b

a

l

V

a

r

staticGlobalVar

staticGlobalVar 被

s

t

a

t

i

c

static

static 修饰,只能在当前文件使用CstaticVar局部静态变量,局部静态变量虽然访问作用域在函数中,但它与全局变量一样,存放在静态区ClocalVar局部变量,局部变量放在 Anum1数组名,数组名表示的是首元素的地址,也可以表示整个数组。这里是后者,即开辟出的10个

i

n

t

int

int 空间,静态数组开辟的空间都是在栈数开辟的A

char2 与上述

n

u

m

1

num1

num1 类似,表示的是开辟出的 5 个

c

h

a

r

char

char 空间(别忘了‘\0’)。 A*char2 这里表示的是数组首元素的地址,解引用即表示第一个元素 ‘

a

a

a’。虽然我们知道常量字符串是存储在常量区,但是,数组中存的 “

a

b

c

d

abcd

abcd” 是从常量区中 拷贝 过来存储在数组中,依然是在上。 ApChar3指针变量,是局部变量,是在栈上开辟 4/8 个空间存储 “

a

b

c

d

abcd

abcd” 首字符的地址的变量。 A*pChar3 指向 “

a

b

c

d

abcd

abcd” 首元素的地址 ‘

a

a

a’,解引用即问 ‘

a

a

a’ 存储在哪。“

a

b

c

d

abcd

abcd” 是只读常量,放在常量区Dptr1

p

C

h

a

r

3

pChar3

pChar3 同理,都是存储在上。 Aptr1 指向动态开辟的空间,而 *ptr1 则是问动态开辟出的空间存储在哪,存储在上。 B

在这里插入图片描述

某种程度来说,分区分的是什么呢?分的是生命周期!不同的对象,声明周期是不一样的

我需要一次性的,就是<code>局部变量,我需要长期使用的就是静态的,我需要动态申请释放的,就是上的,我需要不修改的,就是常量区

我们程序员主要关注的就是堆区,只有堆区是交给程序员自己控制的,其他都是编译器在管

2 C语言 中动态内存管理方式:malloc/calloc/realloc/free

关于上述 4 个函数,在 C语言 的学习中已详细讲解,这里就不再赘述,感兴趣的小伙伴可跳转至此:【C语言】—— 动态内存管理

3 C++ 内存管理方式

3.1 new / delete 操作内置类型

C语言 内存管理方式在 C++ 中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此 C++ 又提出了自己的内存管理方式:通过

n

e

w

new

new 和

d

e

l

e

t

e

delete

delete 操作符进行动态内存管理。

int main()

{

//动态申请一个int类型空间

int* p1 = new int;

//动态申请10个int类型空间

int* p2 = new int[10];

//动态申请一个int类型空间,并初始化为10

int* p3 = new int(10);

//动态申请10个int类型空间,并将前面3个初始化为1,后面默认初始化为0

int* p4 = new int[5] { 1, 1, 1,};

//释放空间

//new匹配delete,new[]匹配delete[]

delete p1;

delete[] p2;

delete p3;

delete[] p4;

return 0;

}

在这里插入图片描述

可以看到,与

m

a

l

l

o

c

malloc

malloc 不同,

n

e

w

new

new 是支持初始化的。

未给值时,默认不初始化。给了值则初始化,只给了部分值,后面默认初始化为0

在这里插入图片描述

3.2 new 和 delete 操作自定义类型

3.2.1 new 和 delete 操作自定义类型基础

C++ 抛弃

m

a

l

l

o

c

malloc

malloc 而自己创建

n

e

w

new

new 和

d

e

l

e

t

e

delete

delete 仅仅是为了上述能够初始化吗?肯定不是的,谜底出现在自定义类型

<code>class A

{

public:

A(int a = 0)

:_a(a)

{

cout << "A(int a)" << endl;

}

~A()

{

cout << "~A()" << endl;

}

void Print()

{

cout << "_a = " << _a << endl;

}

private:

int _a = 0;

};

int main()

{

A* p1 = new A;

A* p2 = new A(10);

p1->Print();

p2->Print();

delete p1;

delete p2;

return 0;

}

运行结果:

在这里插入图片描述

n

e

w

/

d

e

l

e

t

e

new/delete

new/delete 和

m

a

l

l

o

c

/

f

r

e

e

malloc/free

malloc/free <code>最大区别是 :

n

e

w

/

d

e

l

e

t

e

new/delete

new/delete 对于自定义类型除了开空间还会自动调用其构造函数和析构函数

3.2.2 自动调用构造和析构的好处

那有小伙伴问:自动调用构造和析构函数真的那么重要吗?

我们以链表节点来感受一下:

struct ListNode

{

int val;

ListNode* next;

ListNode(int x = 0)

:val(x)

,next(nullptr)

{ }

};

有做过 C++ 相关 OJ 的小伙伴都知道,C++ 中链表是这样给的

这时,我们初始化节点就很方便啦

int main()

{

ListNode* p1 = new ListNode;

ListNode* p2 = new ListNode(1);

ListNode* p3 = new ListNode(2);

p1->next = p2;

p2->next = p3;

delete p1;

delete p2;

delete p3;

return 0;

}

想一下,如果是 C语言 是怎么实现的?是不是还要自己写一个创建节点的函数,而销毁节点是不是也要自己写一个函数。现在好了

n

e

w

new

new 和

d

e

l

e

t

e

delete

delete 自动调用构造和析构,活都帮你干了。

3.2.3 多参数自定义类型

对于多参数的自定义类型,

n

e

w

/

d

e

l

e

t

e

new/delete

new/delete 也同样会调用其构造与析构函数,但如何传参呢?

class A

{

public:

A(int a = 0, int b = 0)

:_a(a)

,_b(b)

{

cout << "A(int a)" << endl;

}

A(const A& a)

{

_a = a._a;

_b = a._b;

cout << "A(const A& a)" << endl;

}

~A()

{

cout << "~A()" << endl;

}

void Print()

{

cout << "_a = " << _a << endl;

}

private:

int _a = 0;

int _b = 0;

};

int main()

{

A* p1 = new A(1, 1);

cout << endl;

//数组多参数给值方法

//法一:严格来说这不是调用构造,而是拷贝构造

A a1(1, 1);

A a2(2, 2);

A a3(3, 3);

A* p2 = new A[3]{ a1,a2,a3 };

cout << endl;

//法二:匿名对象(编译器优化成直接构造)

A* p3 = new A[3]{ A(1,1), A(2,2), A(3,3) };

cout << endl;

//法三:隐式类型转换(编译器优化成直接构造)

A* p4 = new A[3]{ { 1,1},{ 2,2}, { 3,3} };

cout << endl;

return 0;

}

运行结果:

在这里插入图片描述

在这里插入图片描述

3.2.4 不存在默认构造

但如果自定义类型不存在默认构造就需要注意啦

<code>class A

{

public:

A(int a)

:_a(a)

{

cout << "A(int a)" << endl;

}

~A()

{

cout << "~A()" << endl;

}

void Print()

{

cout << "_a = " << _a << endl;

}

private:

int _a = 0;

};

int main()

{

A* p = new A;

return 0;

}

在这里插入图片描述

如果自定义类型不存在默认构造函数,那么编译器会报错

一般情况下,我们不再使用

m

a

l

l

o

c

/

f

r

e

e

malloc/free

malloc/free,因为

n

e

w

/

d

e

l

e

t

e

new/delete

new/delete 兼容了他们所有的用法,还能自动调用构造和析构

3.2.5 空间开辟失败

m

a

l

l

o

c

malloc

malloc 开辟空间,要对返回值进行检查,检查返回值是否为<code>空指针。那用

n

e

w

new

new 开辟空间要不要检查呢?

也是的,

n

e

w

new

new 失败是 抛异常,这里我们简单提一下,更多知识将会后面学习

注:一般情况下,空间开辟不会失败,一般都是空间开的太大才会失败

抛一共有个关键字:

t

h

r

o

w

throw

throw

t

r

y

try

try

c

a

t

c

h

catch

catch

当发生异常,

t

h

r

o

w

throw

throw 抛出一个对象,对异常进行处理要引入

t

r

y

try

try(尝试) /

c

a

t

c

h

catch

catch(捕获)

这里我们申请 2G 的空间试试:

int main()

{

void* p1 = new char[1024 * 1024 * 1024];

cout << p1 << endl;

void* p2 = new char[1024 * 1024 * 1024];

cout << p2 << endl;

return 0;

}

注:

1

G

=

1024

M

1

M

=

1024

K

1

K

=

1024

B

y

t

e

1

B

y

t

e

=

8

b

i

t

1G = 1024M ;1M = 1024K; 1K = 1024Byte ;1Byte = 8bit

1G=1024M;1M=1024K;1K=1024Byte;1Byte=8bit

运行结果:

在这里插入图片描述

申请 第一G 还是没问题的,申请 第二G 程序中断

我们对其进行捕获(先不用管怎么写,知道这是在捕获就行)

<code>int main()

{

try

{

void* p1 = new char[1024 * 1024 * 1024];

void* p2 = new char[1024 * 1024 * 1024];

}

catch (const exception& e)

{

cout << e.what() << endl;

}

return 0;

}

运行结果:

在这里插入图片描述

打印:

b

a

d

a

l

l

o

c

a

t

i

o

n

bad allocation

badallocation,表示<code>已经没有你需要的那么大的内存了。

我们还可以来看一下在 32 位平台下可以申请到多大空间:

void func()

{

int n = 0;

while (1)

{

n++;

void* p = new char[1024 * 1024];//一次向堆申请一字节的内存

cout << p << ":->" << n << endl;

}

}

int main()

{

try

{

func();

}

catch (const exception& e)

{

cout << e.what() << endl;

}

return 0;

}

运行结果:

在这里插入图片描述

我们可以看到,申请了 1898M,大概是 1.85G 。要知道,32 位下的虚拟内存总共也就 4G(

2

32

b

i

t

2^{32} bit

232bit)。其中内核空间就占 1G,栈才分 8M。因此堆空间给了 1.85G,已经很大方了。

在外面平时的练习中,一般是不会申请控价失败的,空间开辟失败一般是因为申请的空间太大。我们平时练习可以不捕获异常,但是在项目中都是需要捕获异常的。

4 operater new 与 operator delete 函数

n

e

w

new

new 和

d

e

l

e

t

e

delete

delete 是用户进行动态内存申请和释放的操作符

C++ 提供了两个全局函数:

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 和

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete。

n

e

w

new

new 在底层调用

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 来申请空间;

d

e

l

e

t

e

delete

delete 在底层调用

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete 来释放空间

下面是

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 和

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete 的源码,我们简单来看一下:

<code>/*

operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间

失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。

*/

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)

{

// try to allocate size bytes

void* p;

while ((p = malloc(size)) == 0)

if (_callnewh(size) == 0)

{

// report no memory

// 如果申请内存失败了,这里会抛出bad_alloc 类型异常

static const std::bad_alloc nomem;

_RAISE(nomem);

}

return (p);

}

对于

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new,我们可以看到在第五行:当malloc = 0,则后面的

i

f

if

if 语句就抛异常。

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 的本质其实就是 调用

m

a

l

l

o

c

malloc

malloc 函数。

/*

operator delete: 该函数最终是通过free来释放空间的

*/

void operator delete(void* pUserData)

{

_CrtMemBlockHeader* pHead;

RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));

if (pUserData == NULL)

return;

_mlock(_HEAP_LOCK); /* block other threads */

__TRY

/* get a pointer to memory block header */

pHead = pHdr(pUserData);

/* verify block type */

_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));

_free_dbg(pUserData, pHead->nBlockUse);

__FINALLY

_munlock(_HEAP_LOCK); /* release other threads */

__END_TRY_FINALLY

return;

}

/*

free的实现

*/

#define free(p) _free_dbg(p, _NORMAL_BLOCK)//free其实是一个宏函数

对于

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete,我们看到 第16行,调用了一个_free_dbg_free_dbg是什么呢?我们来看到最下面

f

r

e

e

free

free,其实

f

r

e

e

free

free 是一个宏,其本质就是 _free_dbg。因此

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete 本质就是调用

f

r

e

e

free

free 函数

通过上述两个全局函数的实现知道,

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 实际也是通过

m

a

l

l

o

c

malloc

malloc 来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete 最终是通过

f

r

e

e

free

free 来释放空间的。

其实,

n

e

w

new

new 和

d

e

l

e

t

e

delete

delete 并没有那么神奇,他们并没有单独搞一套新的申请空间方式,

n

e

w

new

new 其本质上也是调用

m

a

l

l

o

c

malloc

malloc,只是它没有直接调,而是调用套着

m

a

l

l

o

c

malloc

malloc 马甲的

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new。

为什么要套

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 呢?因为

m

a

l

l

o

c

malloc

malloc 失败是直接返回空,C++ 是面向对象的语言,它希望它是按新的一种机制来走,即抛异常。所以抛异常是

o

p

e

e

r

a

t

o

r

opeerator

opeerator

n

e

w

new

new 抛出来的

5 new 和 delete 的实现原理

5.1 内置类型

如果申请的是内置类型的空间,

n

e

w

new

new 和

m

a

l

l

o

c

malloc

malloc,

d

e

l

e

t

e

delete

delete 和

f

r

e

e

free

free 基本类似,不同的地方是:

n

e

w

/

d

e

l

e

t

e

e

new/deletee

new/deletee 申请和释放的是单个元素的空间,

n

e

w

new

new[] 和

d

e

l

e

t

e

delete

delete[] 申请的是连续的空间,而且

n

e

w

new

new 在申请空间失败时会抛异常

m

a

l

l

o

c

malloc

malloc 会返回 NULL

5.2 自定义类型

n

e

w

new

new 的原理

1、 调用 operator new 函数申请空间

2、 在申请的空间上执行构造函数,完成对象的构造

d

e

l

e

t

e

delete

delete 的原理

1、 在空间上执行析构函数,完成对象中资源的清理工作

2、 调用 operator delete 函数释放对象的空间

n

e

w

new

new

T

[

N

]

T[N]

T[N] 的原理

1、调用 operator new[] 函数,在

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new[] 中实际调用

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 函数完成 N 个对象空间的申请

2、在申请的空间上执行 N 次构造函数

d

e

l

e

t

e

delete

delete[] 的原理

1、在释放的对象空间上执行N次析构函数,完成 N 个对象中的资源清理

2、调用

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete[] 释放空间,实际

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete[] 中调用

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete 来释放空间

我们可以通过汇编代码来看一下

n

e

w

new

new 的底层

int main()

{

A* p = new A(1);

delete p;

return 0;

}

在这里插入图片描述

在这里插入图片描述

6 不匹配使用的情况

6.1 new / free

6.1.1 内置类型

<code>int main()

{

int* p1 = new int;

free(p1);

return 0;

}

对内置类型,如果我

n

e

w

new

new 出来,再对

f

r

e

e

free

free 释放,会发生什么?

无事发生

这里我们要看他的本质

n

e

w

new

new 本质是调用

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 加调用构造,

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 本质是调用

m

a

l

l

o

c

malloc

malloc,而内置类型没有构造函数的概念

d

e

l

e

t

e

delete

delete 本质是调用

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete 加调用析构,

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete 本质是

f

r

e

e

free

free,而对内置类型没有调用析构的概念。

因此,程序不会报错,也不会存在内存泄漏的情况

6.1.2 自定义类型

那对自定义类型呢

int main()

{

A* p1 = new A;

free(p1)$;

return 0;

}

首先程序是不会报错的,但相比用

d

e

l

e

t

e

delete

delete,少调用了一个析构函数,有可能会造成资源没有释放,有内存泄漏的风险

6.2 new [ ] / delete

现在,我用

n

e

w

new

new[] 申请多个空间,再用

d

e

l

e

t

e

delete

delete 释放会有问题吗

6.2.1 内置类型

int main()

{

int* p1 = new int[10];

delete p1;

return 0;

}

没有问题的,因为

n

e

w

new

new[] 本质是调用

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new[],

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new[] 中又调用

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new,最终是

m

a

l

l

o

c

malloc

malloc

d

e

l

e

t

e

delete

delete[] 本质是调用

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete[],

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete[] 内调用

o

p

e

r

a

t

o

r

operator

operator

d

e

l

e

t

e

delete

delete,本质是

f

r

e

e

free

free,不涉及什么析构

d

e

l

e

t

e

delete

delete[] 只不过是

d

e

l

e

t

e

delete

delete 多套了一层壳而已。

内置类型不会报错,也不会造成内存泄漏。

6.2.2 自定义类型

那如果我是自定义类型呢?

class B

{

private:

int _b1 = 1;

int _b2 = 2;

};

int main()

{

B* p1 = new B[10];

delete p1;

return 0;

}

运行结果:

在这里插入图片描述

可以看到,也是没问题的

但对类型A呢?

<code>class A

{

public:

A(int a = 0)

:_a(a)

{

cout << "A(int a)" << endl;

}

~A()

{

cout << "~A()" << endl;

}

void Print()

{

cout << "_a = " << _a << endl;

}

private:

int _a = 0;

int _b = 1;

};

int main()

{

A* p1 = new A[10];

delete p1;

return 0;

}

在这里插入图片描述

崩溃了!

为什么呢?同样是自定义类型怎么就区别对待呢?

先问一下,对 类A 和 类B,

n

e

w

new

new[] 应该申请多少空间呢?应该是 80

我们先看类B:

通过汇编代码,我们知道

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new[] 申请了 80 字节

在这里插入图片描述

再看看类A:

在这里插入图片描述

发现A多开了4个字节!

为什么呢?

编译器对于开辟多个对象的数组时,<code>一般会在头上多开辟 4 个字节,用来存储对象的个数,以便

d

e

l

e

t

e

delete

delete[] 知道需要调用几次析构函数,就像上述开 A 的空间一样。但是编译器对 B 并没有额外开 4 个字节

在这里插入图片描述

但是,对<code>类A,编译器返回的是 pa2 位置的地址;对类B是正常返回pb1位置地址

接着,用

d

e

l

e

t

e

delete

delete 将 类A 和 类B 释放,类B 的释放是没有问题的;但是 类A 就有大问题了:

d

e

l

e

t

e

delete

delete 收到的是 pa2 位置的地址,便从 pa2 开始释放空间,但一整块申请的空间只能整块释放,不能从中间释放,因此报错

而用

d

e

l

e

t

e

delete

delete[] 释放,它会往前偏移4个字节再释放,就不会出现这样的问题。

那为什么 类B 不多开 4 个字节呢?因为编译器对B进行了优化,编译器看到 类B 没有去写析构函数,认为没有资源需要去释放,。所以编译器直接就不调用析构函数了,既然不用调用析构函数,那头上也就不用多开 4 个字节存个数了。

如果 B 将析构函数加上,也会多开 4 个字节存个数,也会崩溃

7 定位 new 表达式(placement-new)(了解)

7.1 定位 new 用法

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 是一个全局函数,我们可以显示的去调用

int main()

{

A* p1 = new A(1);

A* p2 = (A*)operator new(sizeof(A));

return 0;

}

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 底层就是

m

a

l

l

o

c

malloc

malloc,因此除了函数名不一样,用法基本是一样的。区别是它不用检查返回值,因为它是抛异常

在这里插入图片描述

区别是

n

e

w

new

new 会自动调用构造函数,

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 只开了空间

那如果我现在想对一块已经存在的空间显示去调用构造函数该怎么做呢?

使用定位new表达式(placement-new)

格式

n

e

w

(

p

l

a

c

e

new (place

new(place_

a

d

d

r

e

s

s

)

t

y

p

e

address) type

address)type 或者

n

e

w

(

p

l

a

c

e

new (place

new(place_

a

d

d

r

e

s

s

)

t

y

p

e

(

i

n

i

t

i

a

l

i

z

e

r

address) type(initializer

address)type(initializer -

l

i

s

t

)

list)

list)

p

l

a

c

e

place

place_

a

d

d

r

e

s

s

address

address 必须是一个指针

i

n

i

t

i

a

l

i

z

e

r

initializer

initializer -

l

i

s

t

list

list 是类型的初始化列表

<code>int main()

{

A* p1 = (A*)operator new(sizeof(A));

new(p1)A(1);//定位new,初始化

return 0;

}

那析构函数怎么调用呢?

对已经分配空间的对象,想构造函数只能通过定位new,但析构函数是可以我们显式调用的

int main()

{

A* p1 = (A*)operator new(sizeof(A));

new(p1)A(1);

p1->~A();

operator delete p1;

return 0;

}

7.2 定位 new 用途

我们要用

o

p

e

r

a

t

o

r

operator

operator

n

e

w

new

new 呢?直接用

n

e

w

new

new 不好吗,这主要是用于内存池

编程中有一种技术叫池化技术,其包括内存池、线程池、连接池……

池化技术都是为了提高效率

那内存池是什么呢?

假设我现在有一个工程,需要频繁地申请释放内存,这样效率难免会较低,毕竟实际中往往是多个任务同时进行。这时,就可以从内存(堆)中专门取出一部分空间,专供这个项目使用。申请从内存池中取空间,释放将空间换给内存池

在这里插入图片描述

但现在还有一个问题:内存池中申请的只有空间,并没有对其初始化,这时 <code>定位new 就有用武之地了。释放空间时,也是我们自己先调用析构函数在释放。

8 malloc/free 和 new/delete 的区别

m

a

l

l

o

c

/

f

r

e

e

malloc/free

malloc/free 和

f

r

e

e

free

free 是函数

n

e

w

/

d

e

l

e

t

e

new/delete

new/delete 是操作符

m

a

l

l

o

c

malloc

malloc 申请的空间不会初始化,

n

e

w

new

new 可以初始化

m

a

l

l

o

c

malloc

malloc 申请空间时,需要手动计算空间大小并传递

n

e

w

new

new 只需在其后跟上空间的类型即可,如果是多个对象 [] 中指定对象个数即可

m

a

l

l

o

c

malloc

malloc 的返回值为

v

o

i

d

void*

void∗ ,在使用时必须强转

n

e

w

new

new 不需要,因为

n

e

w

new

new 后跟的是空间的类型

m

a

l

l

o

c

malloc

malloc 申请空间失败时,返回的是 NULL,因此使用时必须判空

n

e

w

new

new 不需要,但是

n

e

w

new

new 需要捕获异常申请自定义类型对象时,

m

a

l

l

o

c

/

f

r

e

e

malloc/free

malloc/free 只会开辟空间,不会调用构造函数和析构函数,而

n

e

w

new

new 在申请空间后会调用构造函数完成对对象的初始化

d

e

l

e

t

e

delete

delete 在释放空间前会调用析构函数完成空间中资源的清理释放


好啦,本期关于内存管理的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在C语言的学习路上一起进步!



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。