文章目录
一、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";
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);
}
内存中对应的位置:
- 栈:存放函数的参数,局部变量,寄存器的信息 (函数结束,栈帧释放),栈向下 生长
- Linux下默认栈空间大小:8192kb=8M
- Windows下栈空间:1024kb=1M
- 堆:动态内存申请,malloc,calloc,realloc,必须用free释放,堆向上生长
- 数据段:全局变量,被static修饰的数据 (生命周期和程序一致,程序退出数据清空)
- 代码段:代码和只读常量
为什么要对内存划分不同的区域?
数据类型不同,对数据的管理方式也不同,为了方便查找要划分不同区域
二、内存管理
2.1 C语言内存管理:
malloc、calloc、realloc
int* p1 = (int*)malloc(sizeof(int)); // 动态申请int空间大小,随机值
int* p2 = (int*)calloc(4, sizeof(int)); // 动态申请int空间大小,并赋值4
int* p3 = (int*)realloc(p2, sizeof(int) * 10); //调整p2的空间大小为10个int大小
相同点:
- 返回值为void*,接收时必须强转
- 申请失败返回NULL,因此使用之前要判空
不同点:
- malloc(size):只是申请空间大小为size的内存
- calloc(num,size):申请空间大小为size,并赋值为data
- realloc(void* p, size):将p所指的空间大小调整到size大小(扩大或者缩小),若p为NULL则功能和malloc相似。如果p后面的空间大小不足size,则重新找一个空间够size大小,并拷贝旧空间内容,最后释放旧空间大小。
注意:malloc 申请的内存空间会比预计大一点,在对象模型中前面有32字节的属性信息(包括申请内存的大小等),后面有4字节的结束位置,防止越界。
2.2 C++内存管理:
为什么C++会新建立一个内存管理方式?
C++是面向对象程序设计,用malloc、calloc、realloc动态开辟的类空间大小,不会调用构造函数,因此不能称之为对象,调用free释放时,也不会调用析构函数清理对象内部资源,可能造成内存泄露
- 申请/释放 单个类型空间:new/delete
- 申请/释放 连续类型空间:new[]/delete[]
动态申请内置类型:
int* p1=new int(10); 用10初始化
int* p2=new int[10]{1,2,3,4,5,6,7,8,9,0}; 申请连续空间并初始化
delete p1;
delete[] p2;
动态申请自定义类型:
class Test
{
public:
Test(int p = 0)
:p_(p)
{
cout << "构造:" << this << endl;
}
~Test()
{
cout << "析构:" << this << endl;
}
private:
int p_;
};
int main()
{
Test* p1 = new Test(100); //会调用构造函数(类内成员t被初始化成1),因此生成的为对象
Test* p2 = (Test*)malloc(sizeof(Test)); //不会调用(类内成员t没有初始化),因此只能称为和类类型大小相同的堆空间
delete p1; //会调用析构函数,清理
free(p2); //只会释放开辟的空间
return 0;
}
malloc和new的区别:
- new是C++中的关键字,malloc是库函数,因此使用前要引入头文件
- malloc申请/释放空间不会调用构造/析构函数,new会调用构造/析构函数
- malloc申请失败返回NULL,new由于内部实现,不存在返回空指针的情况
- malloc返回值为void*,new返回对应类型的指针,因此不需要强转接收
注意:
new/delete、new[]/delete[]、malloc\free必须匹配使用,否则会造成程序崩溃或者内存泄漏的情况
class Test
{
public:
Test(int val = 0)
:p_(new int(val))
{
cout << "构造:" << this << endl;
}
~Test()
{
delete p_;
cout << "析构:" << this << endl;
}
private:
int* p_;
};
int main()
{
Test* p1 = (Test*)malloc(sizeof(Test));
Test* p2 = new Test;
Test* p3 = new Test[2];
delete p1; // 程序崩溃,malloc没有调用构造函数
delete[] p1; // 程序崩溃,malloc没有调用构造函数
free(p2); // 内存泄漏,free没有调用析构函数
free(p3); // 程序崩溃,连续空间只释放了一部分
delete p3; // 程序崩溃,连续空间只释放了一部分
return 0;
}
三、new/delete的工作流程
3.1 new
- 1.申请堆空间
- 调用void* operator new(size)函数申请空间,size是类的空间大小,内部循环调用malloc申请空间
- 成功:返回空间首地址
- 失败:调用_callnewh(size)函数,调用用户提供的解决方法,若失败则抛出bad_alloc类型异常,继续申请空间
- 调用void* operator new(size)函数申请空间,size是类的空间大小,内部循环调用malloc申请空间
- 2.调用构造函数对申请的空间进行初始化
小结:由于operator new()函数的执行方式,new不会返回空
3.2 delete
- 1.调用析构函数,清理对象中的资源
- 2.调用operator delete()函数,内部调用free释放空间
3.3 new[]
- 1.申请空间,调用void* operator new[] (size),其内部调用了operator new()函数申请N个类空间
- 2.调用N次构造函数,初始化N个对象
3.4 delete[]
- 1.调用N次析构函数对p所指向的空间资源进行清理
- 2.调用void operator delete[] (void* p) 对p所指向的空间进行释放 —>内部调用了operator delete函数
四、将malloc开辟的类空间转换成对象
- 用malloc 申请出来的空间不能称之为对象,因为没有调用构造函数
- 定位new表达式:在已开辟好的堆空间中调用构造函数,初始化类空间,使之成为对象
#include<iostream>
using namespace std;
class Test
{
public:
Test(int t = 0)
:_t(t), _p(new int)
{
cout << "Test(int):" << endl;
}
~Test()
{
delete _p;
cout << "~Test():" << this << endl;
}
private:
int _t;
int* _p;
};
int main()
{
Test* pt = (Test*)malloc(sizeof(Test));
new(pt) Test(100); // 定位new表达式
pt->~Test(); // 调用析构函数清理资源
free(pt); // 调用free释放空间
return 0;
}