目录
三.operator new/operator delete函数
一.C/C++内存分布
在C语言的学习中已经接触过一部分内存管理,这里给出一个练习题来回顾一下:
1. 选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____
staticGlobalVar在哪里?____
staticVar在哪里?____
localVar在哪里?____
num1 在哪里?____
char2在哪里?____
*char2在哪里?___
pChar3在哪里?____
*pChar3在哪里?____
ptr1在哪里?____
*ptr1在哪里?____
答案:CCCAA AAADAB , static修饰的位于静态区;注意指针和指向内容有区别,指针本身存在栈上,而指向的内容若是常量(如常量字符串)则在常量区;易错点在char2[],这是使用常量字符串“abcd”来进行拷贝的一份存于栈区的字符串,因此*char2存在栈区
【说明】
1.栈(Stack)
- 栈是一种用于管理局部变量、函数参数、函数调用返回地址的数据结构
- 它遵循“后进先出”(LIFO)的原则,最后压栈的数据最先被弹出
- 栈是向下增长的,即向低地址的方向增长,这意味着当新的内容被推入栈时,栈指针(或称为栈顶指针)会向下移动,指向更低的内存地址。相应地,当内容从栈中弹出时,栈指针会向上移动,指向更高的内存地址。这种增长方式被称为向下增长。
2.内存映射段(Memory Mapping Segment)
- 这是一种高效的I/O操作方法,通过将文件或其他对象直接映射到进程的地址空间,允许进程像访问内存一样读写这些对象
- 内存映射可以用于创建共享内存区域,从而实现进程间的通信
3.堆(Heap)
- 堆用于程序运行时的动态内存分配,比如通过malloc()、new等函数申请的内存
- 堆的大小可以动态调整,通常是地地址向高地址增长
4.数据段(Data Segment)
- 数据段用于储存全局变量和静态变量
- 在程序启动时,这部分内存就已经初始化了,并且在程序运行期间保持不变
5.代码段(Code Segment)
- 又称为文本段(Text Segment),用于存放编译后的机器码指令和只读常量
- 这部分内存通常是只读的,以防止程序意外修改自身的指令集
C语言的动态内存管理方式:malloc/calloc/realloc/free
void Test ()
{
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}
答:malloc负责开辟空间而不初始化;calloc在开辟开辟空间的同时初始化为0;realloc对原空间进行扩容,若原空间后空间足够,则原地扩容,p2和p3是相同的,但不够的情况下,realloc会新开辟一个空间,将原来旧空间的内容拷贝过来再释放旧空间,因此,p2不需要free
二.C++内存管理方式
1.操作内置类型
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
int main() {
//动态申请一个int类型的空间
int* p1 = new int;
//动态申请一个int类型的空间,并初始化为10
int* p2 = new int(10);
//动态申请一个10个int类型的空间
int* p3 = new int[10];
//释放
delete(p1);
delete(p2);
delete[](p3);
}
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[ ]和delete[ ],一定要注意:匹配起来使用,否则有可能会出错
2.操作自定义类型
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A* p1 = new A;
delete(p1);
cout << "-------------------------" << endl;
A* p2 = new A[5];
delete[](p2);
return 0;
}
根据上图展示的结果,可以发现使用new关键字来开辟空间时会自动调用构造函数,同时delete也会自动调用析构函数 ,这就是new/delete与malloc/free的最大区别,因此,在内置类型时两者几乎相同,但对于自定义类型而言,就更推荐使用new/delete了。
还值得注意的是:此处new了5个对象,就调用了5个构造,对应delete[]时就调用5次析构,因此,千万不能弄混,new/delete和new[]/delete[]一定要搭配使用,否则会出现错误,例如:
拓展:有无析构函数调用delete的区别
class A
{
public:
A(int a = 0)
: _a(a)
{}
~A()
{}
private:
int _a = 1;
};
class B
{
private:
int _b = 2;
};
int main()
{
A* p3 = new A[10];
B* p2 = new B[10];
delete[] p3;
delete p2;
}
通过调试汇编代码,可以发现大小相同的类型A、B两类,同样是开辟数量为10个,p3却比p2大了4个字节,这就是因为A是有析构函数,而B是没有的。
p3多出的这4个字节是来在储存new的对象的个数的,这个数据是交给delete[]来使用的;而p2没有析构函数,编译器发现B没有额外的资源需要释放,因此省去了析构这一步,直接调用operator delete进行释放,在这里而言,p2使用delete而不使用delete[]也是能够使程序成功运行的。
但对于p3而言,若直接使用delete就会从p3位置处释放空间,而空间的真正其实位置是还要靠前的,这样就会引发错误。
三.operator new/operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
而operator new实际上是通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败时,会尝试执行空间不足的应对措施,如果用户设置了应对措施,则继续申请,否则抛出异常。同理,operator delete实际是通过free来释放空间的。
也就是说new的时候会先调用operator new函数,然后再调用构造函数,在汇编代码下可以看见:
delete的时候先调用析构函数,再调用operator delete来释放空间:
四.new/delete原理总结
1.内置类型
对于内置类型如int,double,char等,new和malloc都能用来分配内存,但是存在以下差异:
- new在分配失败时会抛出异常,而malloc会返回NULL
- new和delete通常用于单个元素,而new[]和delete[]用于分配和释放连续的数组空间
2.自定义类型
- new通过调用operator new函数来分配内存,并且在分配的内存上构造对象,调用相应的构造函数
- delete通过调用对象的析构函数进行必要的资源清理,再调用operator delete函数来释放内存
- new[ ]调用operator new[ ]来分配足够多的内存,并对每个元素调用构造函数
- delete[ ]对每个元素调用析构函数,再调用operator delete[ ]函数来释放内存
总之,
new
和delete
不仅分配和释放内存,还负责构造和析构对象,这使得它们成为管理自定义类型时更强大的工具,而malloc
和free
主要用于简单的内存管理,不涉及构造和析构过程。
五.malloc/free和new/delete区别
malloc
和free
与new
和delete
在C++中用于动态内存管理,但它们之间存在关键的区别。下面总结了两者的主要共同点和不同之处:1.共同点
- 堆内存分配:
malloc
和free
以及new
和delete
都用于从堆内存中分配和释放空间- 手动管理:无论是使用
malloc
/free
还是new
/delete
,程序员都必须显式地管理内存,包括分配和释放2.不同点
- 性质:malloc和free是函数,new和delete是操作符
- 初始化:malloc申请的空间不会初始化,new可以初始化
- 类型安全和大小计算:malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
- 返回值和类型转换:malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- 错误处理:malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需 要捕获异常
- 自定义类型对象的构造和析构:申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放