当前位置:   article > 正文

C++——内存管理_c++内存管理

c++内存管理

目录

1.c++内存分布

2.c语言中动态内存管理

3.c++动态内存管理

4.operator new 和 operator delete 函数

5. new和delete的实现原理

6.定位new表达式(了解)

7. malloc/free 和 new/delete 的区别

8.什么是内存泄漏,内存泄漏的危害


1.c++内存分布

内核空间用户代码不能读写
向下增长
内存映射段文件映射、动态库、匿名映射
向上增长
数据段 或静态区全局数据、静态数据
代码段 或常量区可执行代码/只读常量

代码是存在文件里的,文件存在磁盘里(不是在栈或者代码段上)

上面的内存分布是针对虚拟进程地址空间划分的。

每个进程编译好是一个可执行程序,可执行程序会运行起来,运行起来以后就把全局变量、静态变量、常量加载到相应区域,栈和堆里的数据不需要从文件中加载进来提前开好空间。局部变量只有在程序运行起来以后main函数执行,才开始建立栈帧,main函数调用下一个函数,继续建立栈帧。堆也是,不用提前给它开空间,要用的时候申请就行。

虚拟进程地址空间不止有这几个区域,在Linux下,32位程序有2^32个地址,每个地址占一个字节,每个地址都有一个编码。平时我们说的指针其实就是地址(虚拟进程地址空间里面一个一个空间的编号)。

空指针指向一个实际的空间,编号为32个0的空间。

指针是用来存地址编码的,一个编码是32位数字,就需要4字节来存,指针大小就是4字节。

2^322^64
32位机器(指针4字节)64位机器(指针8字节)
4G

2^32*4G

练习:

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 区别?

2.c语言中动态内存管理

malloc: void* malloc (size_t size);//size是申请空间大小,单位字节

calloc:void* calloc (size_t num , size_t size);//num是申请元素个数,size是每个元素大小,单位字节。calloc还会自动把开辟的空间,每一个字节都初始化为0。

realloc:void* realloc (void* ptr , size_t size);//ptr是起始地址,size是扩容后的总大小,单位字节

3.c++动态内存管理

以开辟整形数组为例:使用new 和 delete,在申请的时候就可以初始化

  1. int main()
  2. {
  3. //内置类型
  4. //相比malloc和free,除了用法的区别没有其他区别
  5. int *p1 = new int;//用new申请空间,不初始化
  6. delete p1;//释放
  7. int* p2 = new int(0);//初始化成0
  8. delete p2;
  9. int* p3 = new int[10];//开辟10个int
  10. delete[] p3;
  11. int* p4 =new int [10] {1,2,3,4 };//前四个是1234,后面的都是0
  12. delete[]p4;
  13. return 0;
  14. }

new和delete对自定义类型会调构造和析构函数,而自malloc不调。

  1. class A
  2. {
  3. public:
  4. A(int a = 0)
  5. :_a(a)
  6. {
  7. cout << "A():" <<this << endl;
  8. }
  9. ~A()
  10. {
  11. cout << "~A():" << this << endl;
  12. }
  13. private:
  14. int _a;
  15. };
  16. int main()
  17. {
  18. A* p1 = new A;//自定义类调构造函数
  19. A* p2 = (A*)malloc(sizeof(A));//malloc不调构造
  20. delete p1;//调析构函数
  21. free(p2);//不调析构
  22. return 0;
  23. }

用类实现链表:再也不用写BuyNode了(给新节点用malloc开辟空间,还要初始化新节点),new一下自动调构造函数。

  1. class ListNode
  2. {
  3. public:
  4. ListNode(int val=0)
  5. :_next(nullptr)
  6. ,_val(val)
  7. {}
  8. //private:
  9. ListNode * _next;
  10. int _val;
  11. };
  12. int main()
  13. {
  14. ListNode* n1 = new ListNode(1);
  15. ListNode* n2 = new ListNode(2);
  16. ListNode* n3 = new ListNode(3);
  17. n1->_next = n2;
  18. return 0;
  19. }

new和delete要匹配,new[ ]和delete[ ]配对。new和free也不要混合用。

不匹配可能报错也可能不报错。反正就是要匹配用,就不会出问题。

例:

  1. class A
  2. {
  3. public:
  4. A(int a = 0)
  5. :_a(a)
  6. {
  7. cout << "A():" << this << endl;
  8. }
  9. ~A()
  10. {
  11. cout << "~A():" << this << endl;
  12. }
  13. private:
  14. int _a;
  15. };
  16. int main()
  17. {
  18. //以下所有情况都不一定,编译器不同,报不报错也不一定。这里说的报错指vs环境下会报错
  19. A* p3 = new A[10];
  20. delete p3;//会报错,且只调了一次析构函数。不写10的话,这一行不知道有多少个对象,就不知道调多少次析构函数。底层只知道总空间有多少个字节,但不知道对象大小,也就不知道对象个数。
  21. //在堆上开辟空间的时候不是开辟40字节(一个A的大小是4字节),实际申请空间是44,头四个字节(一个整形的大小)用来存对象个数,返回的时候把指针加4个字节,返回对我们来说有用的部分。释放的时候写了[]个数的话,指针又减4字节,取到对象个数来确定调多少次析构函数
  22. //屏蔽掉析构函数之后,就不报错了。原因:编译器自动生成析构函数,发现A只有一个内置类型,也不用释放其他的堆上的空间,所以调不调析构都无所谓,直接不多开四个字节来记录对象个数,这个个数就是用来让编译器知道调多少次析构函数的。优化后也就不报错了,释放位置不会发生偏移
  23. //————————--再看一个例子
  24. A* p4 = new A;
  25. delete[]p4;
  26. //vs报错
  27. //new了一个对象,delete的时候用的[],编译器以为前面存了对象个数,是new[]出来的。会让指针往前偏移,释放就出问题了
  28. return 0;
  29. }

检查是否开辟成功

malloc失败,返回空指针。

new失败,抛异常。不需要检查返回值。 

  1. int main()
  2. {
  3. try
  4. {
  5. int* p1 = new int[20];
  6. }
  7. catch (exception& e)
  8. {
  9. cout << e.what() << endl;
  10. }
  11. return 0;
  12. }

底层机制:

new

1.operator new (它的底层又是malloc)

2.调用构造函数

operator new是库里面实现的全局函数,不是new的运算符重载(运算符重载参数必须有自定义类型)。void * operator new(size_t size)

delete的底层是operate delete(它底层是free)。

4.operator new 和 operator delete 函数

也可以手动调用operator new函数来动态开辟,但没必要,用new就行。

	char* p2 = (char*)operator new(1);//括号里传想要空间的字节数,返回值是void*,要强转。
  1. class A
  2. {
  3. public:
  4. A(int a = 0)
  5. :_a(a)
  6. {
  7. cout << "A():" << this << endl;
  8. }
  9. ~A()
  10. {
  11. cout << "~A():" << this << endl;
  12. }
  13. private:
  14. int _a;
  15. };
  16. int main()
  17. {
  18. A* p1 = new A[10];
  19. delete p1;//会报错,之前讲过了
  20. //屏蔽掉析构函数之后就不报错(没有多开四个字节),有没有内存泄漏
  21. delete[] p1;
  22. free(p1);
  23. //delete和free的区别就是会不会调析构函数
  24. //但是这里析构函数调不调都无所谓的情况下(屏蔽掉析构函数,编译器就自己优化不调析构函数了),这俩底层是一样的,没有内存泄漏
  25. return 0;
  26. }

5. new和delete的实现原理

对内置类型:
new/delete申请或释放单个元素空间,new []/delete []申请或释放连续的空间。

申请失败new抛异常,malloc返回NULL。

对自定义类型:

new先调operator new申请空间,然后调构造函数

delete先调析构函数释放对象中资源,然后调operator delete释放对象本身

new [],先调operator new[]开空间(operator new[]实际上是在调operator new),然后调n次构造。

delete [],先调n次析构函数,再调delete []

6.定位new表达式(了解)

对自定义类型,new是开空间加调用构造函数初始化。

定位new表达式是在已经分配的原始内存空间调用构造函数初始化一个对象。


定位new,和new的不同就是不想用失败抛异常,就可以用定位new

new ( place_address ) type 或 new ( place_address)  type ( initializer-list )

place_address是指针, initializer-list是类型的初始化列表

new ( place_address ) type:

  1. class A
  2. {
  3. public:
  4. A(int a = 0)
  5. :_a(a)
  6. {
  7. cout << "A():" << this << endl;
  8. }
  9. ~A()
  10. {
  11. cout << "~A():" << this << endl;
  12. }
  13. private:
  14. int _a;
  15. };
  16. int main()
  17. {
  18. A* p3 = (A*)malloc(sizeof(A));//malloc出来的不会调构造函数
  19. if (p3 == nullptr)
  20. {
  21. perror("malloc fail\n");
  22. exit(-1);
  23. }
  24. //定位new,和new的不同就是不想用失败抛异常,就可以用定位new
  25. new(p3)A(1);//对p3指向空间,调用构造函数初始化
  26. p3->~A();//析构函数可以直接显示调用
  27. free(p3);
  28. 直接用delete行不行,也可以,底层是一样的
  29. //delete(p3);
  30. return 0;
  31. }

7. malloc/free 和 new/delete 的区别

相同点:

都是在堆上动态申请空间,且需要手动释放。

不同点:

1.malloc和free是函数,new和delete是操作符

2.malloc申请空间不会初始化,new自定义类型会初始化

3.malloc申请空间需要手动计算空间大小并传参,new 只需要写上类型名,开辟多个对象,在[ ]里指定个数即可

4.malloc的返回值类型为void* ,接收时必须强制类型转换,new不需要,new后跟的就是类型名

5. malloc失败返回NULL,使用时必须判断是否开辟成功。new不需要判空,但是要捕获异常

6.为自定义类型申请空间时,malloc和free不会调构造和析构函数。new在申请空间后会调析构函数初始化对象,delete在释放空间前会调用析构函数完成资源清理。

8.什么是内存泄漏,内存泄漏的危害

内存泄漏并不是指内存消失了,而是指针丢失,失去对改该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:程序正常结束,内存泄漏了最终还是会还给操作系统。但长期运行的程序如果出现内存泄漏,影响很大,会导致响应越来越慢,最终卡死。         

练习:

1.C++中关于堆和栈的说法,哪个是错误的( )

A.堆的大小仅受操作系统的限制,栈的大小一般较小

B.在堆上频繁的调用new/delete容易产生内存碎片,栈没有这个问题

C.堆和栈都可以静态分配

D.堆和栈都可以动态分配

A.堆大小受限于操作系统,而栈空间一般由系统直接分配

B.频繁的申请空间和释放空间,容易造成内存碎片,甚至内存泄漏,栈区由于是自动管理,不存在此问题

C.堆无法静态分配,只能动态分配

D.栈可以通过函数_alloca进行动态分配,不过注意,所分配空间不能通过free或delete进行释放

2.ClassA *pclassa=new ClassA[5];

delete pclassa;

c++语言中,类ClassA的构造函数和析构函数的执行次数(D)

A.5,1

B.1,1

C.5,5

D.程序可能崩溃

申请对象数组,会调用构造函数5次,delete由于没有使用[],此时只会调用一次析构函数,但往往会引发程序崩溃。

3.使用 char* p = new char[100]申请一段内存,然后使用delete p释放,有什么问题?(B)
A.会有内存泄露
B.不会有内存泄露,但不建议用
C.编译就会报错,必须使用delete []p
D.编译没问题,运行会直接崩溃


答案解析:
A.对于内置类型,此时delete就相当于free,因此不会造成内存泄漏

B.正确

C.编译不会报错,建议针对数组释放使用delete[],如果是自定义类型,不使用方括号就会运行时错误

D.对于内置类型,程序不会崩溃,但不建议这样使用

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号