1. C/C++内存分布
首先我们需要知道,在C++中的内存分为5个区。
那么为什么要划分这么多区域呢?首先数据是需要存储的,那么不同的数据就有不同的性质,内存区域的划分就是为了满足数据存储的各种需求。
栈区可以建立栈帧,函数的一些临时变量在函数执行完之后就不需要了,可以直接销毁,函数结束了之后,栈帧会自动销毁,所以这些临时变量就会销毁。
堆区是为了满足数据动态使用的需求,在数据结构中会大量的使用动态内存,销毁的时间由自己决定,自己不需要了就可以自行销毁。
一些全局变量,静态的数据存放在静态区,整个程序在运行期间都需要这些数据,所以这些数据要一直存在,作用域是全局的。
可执行代码和不修改的数据(常量)存放在常量区。
我们先来看下面的一段代码和相关问题。
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);
}
前面三个肯定是存在静态区的,这个没有疑问。locaval是存在栈区的,num1也是放在栈区,因为都是临时创建出来的,存放在栈区。
char2也是临时变量,存放在栈区。*char2也是存放在栈区。pchar3也是在栈区,虽然有const修饰,但是需要注意的是这个const修饰的是这个指针指向的内容,而不是这个指针。所以*pchar3存放在常量区,因为这个指针被const修饰。ptr1是创建出来的一个临时变量,所以在栈上面,*ptr1指向的是动态开辟出来的空间,而这块空间是在堆上面。
我们再来看一下下面这些填空题。
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);
}
需要注意的是strlen算的是长度不算'\0',sizeof算的是大小,'\0'也开了空间的,指针的大小取决于当前编译器是32bit位还是64bit位。
2. C语言中动态内存管理方式:malloc/calloc/realloc/free
1. malloc/calloc/realloc的区别?
calloc与malloc的区别是calloc等于malloc+memset,将开辟的空间全部初始化成0.realloc是调整空间大小,如果这个空间还没有开辟,那么第一次realloc相当于一次malloc。
3. C++内存管理方式
3.1 new/delete操作内置类型
C++开辟空间使用new+类型,如果需要开多个空间,在类型后加[]。
int main()
{
int* p1 = new int;
int* p2 = new int[4];
return 0;
}
对内置类型,可以看到开出来的空间并不会初始化,是随机值。
那么想初始化怎么办?
如果是单个值的话在类型后面加(),多个值则加{}。
int main()
{
int* p3 = new int(1);
int* p4 = new int[4] {4, 6, 8};
return 0;
}
如果开了10个int的空间,只初始化了前面几个,那么后面的数据会默认初始化成0.
那么怎么释放呢?C++规定new出来的空间使用delete来释放。
int main()
{
int* p1 = new int;
int* p2 = new int[4];
int* p3 = new int(1);
int* p4 = new int[4] {4, 6, 8};
delete p1;
delete[] p2;
delete p3;
delete[] p4;
return 0;
}
3.2 new和delete操作自定义类型
我们来看下面这段代码,如果使用malloc为自定义类型的对象开空间的话,就没办法初始化,因为自定义类型的初始化在构造函数里面,构造函数是对象实例化的时候自动调用的。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
A* p1 = (A*)malloc(sizeof(A));
return 0;
}
所以就要换成new来开空间,new会做两件事情,第一就是开空间,第二是调用此自定义类型的默认构造函数,如果没有默认构造函数则会报错。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
A* p2 = new A(1);
delete p2;
return 0;
}
delete也会做两件事,第一件事是调用该自定义类型的析构函数,第二件事是释放空间。就相当于先清理资源,再释放掉空间。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
A* p2 = new A(1);
delete p2;
A* p3 = new A[10];
delete[] p3;
return 0;
}
注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与 free不会。
4. operator new与operator delete函数
5. new和delete的实现原理
5.1 内置类型
5.2 自定义类型
6. 常见面试题
6.1 malloc/free和new/delete的区别
malloc/free 和 new/delete 的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地 方是:1. malloc 和 free 是函数, new 和 delete 是操作符2. malloc 申请的空间不会初始化, new 可以初始化3. malloc 申请空间时,需要手动计算空间大小并传递, new 只需在其后跟上空间的类型即可, 如果是多个对象,[] 中指定对象个数即可4. malloc 的返回值为 void*, 在使用时必须强转, new 不需要,因为 new 后跟的是空间的类型5. malloc 申请空间失败时,返回的是 NULL ,因此使用时必须判空, new 不需要,但是 new 需要捕获异常6. 申请自定义类型对象时, malloc/free 只会开辟空间,不会调用构造函数与析构函数,而 new 在申请空间后会调用构造函数完成对象的初始化,delete 在释放空间前会调用析构函数完成 空间中资源的清理
6.2 内存泄漏
6.2.1 什么是内存泄漏,内存泄漏的危害
6.2.2如何避免内存泄漏
1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。 ps : 这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智 能指针来管理才有保证。2. 采用 RAII 思想或者智能指针来管理资源。3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。4. 出问题了使用内存泄漏工具检测。 ps :不过很多工具都不够靠谱,或者收费昂贵。总结一下 :内存泄漏非常常见,解决方案分为两种: 1 、事前预防型。如智能指针等。 2 、事后查错型。如泄 漏检测工具。
今天的分享到这里就结束了,感谢大家的阅读!