一般我们在栈中定义的变量和数组都是开辟固定的内存空间,一旦确定空间的大小就不能改变,通过引入动态内存申请我们就能够自由申请和释放空间,既能满足实际需求,也很大程度上节省了空间大小。
一、C/C++ 中内存分配区域
1.栈区(stack):主要存放运行函数分配的局部变量、函数参数、返回数据、返回地址等。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束,这些存储单元自动被释放。
2.堆区(heap):这个区域就是我们可以自由申请空间的区域,但是用完数据之后,我们要记得释放申请的空间,避免造成内存泄漏。
3.静态区(数据段)(static):存放全局变量、静态数据。程序结束后由系统释放。
4.常量区(代码段)(const):存放函数体(类成员函数和全局函数)的二进制代码及常量。
int globalv = 1;
static int staticglobalv = 1;
int main() {
static int b = 1;
int a = 1;
char c1[] = "abcd";//将abcd\0拷贝到数组c1中,故c1只是临时变量
const char* c2 = "abcd";
//指针c2指向的是"abcd\0"字符串的地址,而地址的内容是字符串,具有常性,
//故*c2存放在常量区。
int* ptr1 = (int*)malloc(sizeof(int));
int* ptr2 = (int*)calloc(1, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int));
int* ptr4 = new int(1);
return 0;
}
下图中的指针均表示指针中存放的内容所属的区域,并非表示指针本身所属的区域。
二、下面将介绍动态内存管理中函数及操作符的用法。
1、malloc
功能:直接在堆中申请一个连续空间,其空间大小由malloc()内的形参决定(单位为字节),开辟成功之后,返回开辟的指针空间的首地址(void*);开辟失败则返回空指针。
int main(){
int* ptr1 = (int*)malloc(sizeof(int));
//括号里面为我们所需要开辟的字节数,地址中存放的数据为随机值。
free(ptr1);
return 0;
}
2、calloc
功能:在堆中申请一个连续空间,并且将指针指向的内容初始化为0,函数返回值和malloc一样。
int main(){
int* ptr2 = (int*)calloc(1, sizeof(int));
//第二个参数为开辟的空间中存储数据的类型;第一个参数为开辟该类型空间的个数
free(ptr2);
return 0;
}
3、realloc
功能:对堆上已有的空间进行扩容,不初始化内存中的数据,函数返回值和malloc一样。
int main() {
int* ptr2 = (int*)calloc(1, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int));//ptr2为空时,作用和malloc一样。
//对堆区的空间进行扩容:
//方式一:直接在ptr2后面开辟一个连续空间满足所需,返回ptr2的地址。
//方式二:如果ptr2后面的连续空间不足所需的大小,
//那么将在堆上重新申请一块能满足要求的内存空间,
//然后把原来ptr2中的数据拷贝到新空间中,并且释放原来的prt2空间,返回新申请内存的地址。
free(ptr3);//这里ptr2和ptr3指向同一内存地址,只需释放其中一个即可。
return 0;
}
4、new
new是C++中独有的在堆区开辟内存空间的表达方式,最后也需要我们手动释放。与malloc的使用方法也有很大的不同。
new | malloc |
操作符的形式简化使用方法 | 函数的形式 |
申请的空间可以直接初始化 | 申请的空间不能直接初始化 |
申请空间类型描述简单 | 要进行强制转换 |
能调用构造函数完成对象初始化 | / |
(1)new的基本用法
int main(){
int* ptr4 = new int;//申请一个int类型的空间
int* ptr5 = new int(1);//申请一个int类型的空间,并将其内容初始化为1。
int* ptr6 = new int[5];//申请5个int类型的空间,不初始化内容。
int* ptr7 = new int[5]{1,2};
//申请5个int类型的空间,并将前两个初始化为1,2,其他初始化为0。
delete ptr4;//销毁空间,与new配套使用
delete ptr5;
delete[] ptr6;//销毁空间,与new[]配套使用
delete[] ptr7;
return 0;
}
(2)new操作自定义类型
表达式:new+空格+类名+(初始化参数)
class M {
public:
M(int a = 0, int b = 0)//初始化列表
:_a(a)
, _b(b)
{
cout << "M" << endl;
}
~M() {
cout << "~M" << endl;
}
private:
int _a;
int _b;
};
int main() {
M* ptr8 = (M*)malloc(sizeof(M));
M* ptr9 = new M(1,2);
free(ptr8);
delete ptr9;
return 0;
}
由调试器可看出,ptr9中_a,_b被初始化,说明在申请内存空间的同时直接调用了构造函数(初始化列表),而ptr8中的数据无法被初始化。
5、free和delete
二者都是对堆区内存空间的释放,我们在使用完申请的空间后一定要释放空间,同时对于同一空间而不能多次释放;
二者的最大区别就是free释放空间是不能调用析构函数,而delete可以;因此对于自定义类型而言,一定要用delete来进行释放空间,避免类中变量申请的空间没有释放造成内存泄漏。
class M {
public:
M(int a = 0, int b = 0, int n = 4)//初始化列表
:_a(a)
,_b(b)
{
_arr = new int[n];//类中元素在堆上申请空间
cout << "M" << endl;
}
~M() {
cout << "~M" << endl;
}
private:
int _a;
int _b;
int* _arr;
};
int main(){
M* ptr8 = (M*)malloc(sizeof(M));
M* ptr9 = new M(1,2);
free(ptr8);
delete ptr9;//调用析构函数,释放_arr
return 0;
}
输出结果: