C/C++内存分布管理
文章目录
一、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);
}
1. 选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____ staticGlobalVar在哪里?____
staticVar在哪里?____ localVar在哪里?____
num1 在哪里?____
char2在哪里?____ *char2在哪里?___
pChar3在哪里?____ *pChar3在哪里?____
ptr1在哪里?____ *ptr1在哪里?____
2. 填空题:
sizeof(num1) = ____;
sizeof(char2) = ____; strlen(char2) = ____;
sizeof(pChar3) = ____; strlen(pChar3) = ____;
sizeof(ptr1) = ____;
3. sizeof 和 strlen 区别?
我们可以看下这张图来解答出我们之前提出的问题
答案是:C C C A A A A A D A B
为什么会是这个答案呢,这里做出说明在 C/C++中,对于全局变量和static修饰的变量和函数,都是放在静态区的,对于函数内没有用static修饰的变量都存在栈上,生命周期是随着栈桢的创建与销毁(函数的调用即创建,函数执行完即销毁),对于malloc或calloc,realloc等开辟空间的,都是在我们最多空间的堆上开辟的,常量是指无法修改的都是放在代码段的,比如pchar3(加解引用了)所指向的是常量。
二、C语言中动态内存管理方式:malloc/calloc/realloc/free
int main()
{
//我们用malloc开辟的空间,是不会帮我们初始化的
//同时我们需要给出需要开辟的空间大小,开辟好的空间返回的是void*
int* p1 = (int*)malloc(sizeof(int));
printf("%p\n",&(*p1));
//开辟好的内存不用了,一定要free释放掉,否则会造成内存泄漏的问题
free(p1);
//我们用calloc开辟的空间,是可以初始化的,我们的第一个参数就是初始化的值
//同时我们需要给出需要开辟的空间大小,开辟好的空间返回的是void*
int* p2 = (int*)calloc(11,sizeof(int));
printf("%p\n",&(*p2));
//realloc是在我们已开辟好的空间上,二次开辟,如果重新开辟的空间
//没有刚好足够连续的空间大小
//则会在其他内存位置重新开辟,返回的是void*
int* p3 = (int*)realloc(p2,sizeof(int)*2);
printf("%p\n",&(*p3));
//realloc重新开辟的空间,有可能返回的是旧地址
//如果是新地址,编译器会帮我们释放掉旧地址的空间,所以我们不用释放p2所开辟的空间
free(p3);
return 0;
}
三、C++中动态内存管理
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
int main()
{
//c++的new delete 对于创建内置类型(int,char''''')
//在C++中的内存管理用的是new 和delete的操作符
//new空格后面加上类型,同时也可以一个括号加上我们想初始化的值
int* p1 = new int(101);
cout<< *p1 << endl;
//delete相当于我们的free,只不过在用法上不一样
delete p1;
//如果你想申请多个 可以在类型后面加方括号【】,在里面填上需要的数量,如果想初始化的话
//需要再加个花括号{里面是我们想初始化的值,是按顺序分配的},未分配的值还是随机值;
int* p2 = new int[10]{11,22,33,44,55};
for(int i = 0;i<10;i++)
{
cout << p2[i] <<" ";
}
//如果我们是用方括号【】new创建多个空间的话,我们释放的时候也要加上方括号,相匹配
//不需要加上具体的大小
delete []p2;
return 0;
}
C++中创建内置类型时跟C语言是一样的,最主要的区别是,在C++中的new delete在创建自定义类型时会自动调用构造函数,和析构函数。
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A()" <<endl;
}
~A()
{
cout << "~A()" <<endl;
}
private:
int _a;
};
int main()
{
//如果我们new一个自定义类型的话,编译器会自动调用我们的构造函数,和析构函数
//这是跟C语言开辟空间最大的区别
A* p1 = new A;
delete p1;
//我们创建几个对象,编译器就会调用几次构造函数,和析构函数,同时要注意我们是用[]创建的多个对象
//释放的时候,也必须加上[]
A* p2 = new A[10];
delete[] p2;
return 0;
}
为什么我们用new不用强转,因为我们是传了一个类型参数的,编译器以这个类型开辟好空间传递回来,new如果报错是不会返回null的,而是会抛异常,我们需要去捕获他,如果在new出错时,编译器会无条件的跳转到捕获的函数。
四、operator new与operator delete函数
operator new和operator delete 是全局函数,而我们的new和delete是动态申请空间的操作符,new和delete的底层都是通过调用operator new和 operator delete这两个函数实现的。
operator new的代码说明
operator delete的代码说明
operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的
五、new和delete的实现原理
内置类型
如果申请的是内置空间类型的话,new和delete 跟malloc 和free是非常类似的,new的实现是通过调用operator new全局函数(这里不是运算符重载),而operator new函数也是通过调用malloc实现的,delete的实现是通过调用operator delete全局函数(这里不是运算符重载),而operator delete也是通过free实现的。
自定义类型
new的实现原理
首先是通过调用operator new 全局函数,实现空间的开辟,然后再去调用我们的构造函数。
delete的实现原理
首先调用我们的析构函数,完成资源的回收,然后再通过调用operator delete全局函数进行空间的释放
new[]的实现原理
根据[]方括号里面的值,调用N个 operator new 全局函数,实现空间的开辟,然后再调用N个构造函数。
delete[]的实现原理
根据[]方括号里面的值,调用N个 析构函数,然后再调用N个调用operator delete全局函数进行空间的释放。
六、定位new表达式(placement-new)
定位new跟c语言中的realloc类似,是在已经开辟好的空间基础上,再次申请空间,不过不同的是还可以显式调用构造函数,
class A
{
//友元函数,之前的博客有介绍
friend inline ostream& operator<<(ostream& out,const A& d);
public:
A(int a = 0)
:_a(a)
{
//cout << "A()" <<endl;
}
~A()
{
cout << "~A()" <<endl;
}
private:
int _a;
};
//运算符<<重载之前的博客有介绍
inline ostream& operator<<(ostream& out,const A& d)
{
out << d._a << endl;
return out;
}
int main()
{
//new的底层原理是通过调用operator new全局函数实现的
//我们也可以调用这个函数来替代new
A* p1 = (A*)operator new(sizeof(A));
cout << *p1 << endl;
//显式调用构造函数
new(p1)A(11);
cout << *p1 << endl;
//delete的底层也是调用析构函数来实现的,我们也可以用直接显式调用析构函数
//然后再用free释放空间
p1->~A();
free(p1);
}
七、常用面试题
malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
内存泄漏
什么是内存泄漏,内存泄漏的危害
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死
C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产Heap Leak。
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定