欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 美食 > C/C++内存管理:从基础到进阶

C/C++内存管理:从基础到进阶

2025/4/18 21:08:11 来源:https://blog.csdn.net/nplplus/article/details/147126411  浏览:    关键词:C/C++内存管理:从基础到进阶

目录

引言

一、C/C++内存分布

1.1 内存区域划分

1.2 代码示例分析

二、C语言中动态内存管理方式

2.1 malloc

2.2 calloc

2.3 realloc

2.4 free

三、C++内存管理方式

3.1 new/delete操作内置类型

3.2 new/delete操作自定义类型

四、operator new与operator delete函数

4.1 原理

4.2 代码示例

五、new和delete的实现原理

5.1 内置类型

5.2 自定义类型

六、定位new表达式(placement - new)

6.1 概念

6.2 使用格式

6.3 使用场景

七、内存泄漏相关问题

7.1 内存泄漏概念

7.2 内存泄漏分类

7.3 如何检测内存泄漏

7.4 如何避免内存泄漏

八、常见面试题

8.1 malloc/calloc/realloc的区别

8.2 malloc的实现原理

总结


引言

在C和C++编程中,内存管理是一个至关重要的话题。合理的内存管理可以确保程序高效、稳定地运行,避免内存泄漏等问题。本文将深入探讨C/C++内存管理的各个方面,结合代码示例详细讲解,帮助大家更好地理解和掌握这一关键技能。

一、C/C++内存分布

1.1 内存区域划分

C/C++程序的内存通常分为以下几个区域:

- 栈(Stack):用于存储局部变量、函数参数等。栈向下增长,具有自动管理内存的特点,变量生命周期随函数结束而结束。例如:

cppvoid Test() {int localVar = 1; // localVar存储在栈中}

- 堆(Heap):用于动态内存分配,由程序员手动申请和释放。例如使用 malloc 、 calloc 、 realloc (C语言)或 new (C++)来分配内存,使用 free (C语言)或 delete (C++)来释放内存。

cppint* ptr = new int; // 在堆上分配一个int类型的空间delete ptr; // 释放堆上分配的空间

- 数据段(静态区):存储全局变量和静态变量。全局变量和静态全局变量在程序启动时分配内存,程序结束时释放。

cppint globalVar = 1; // globalVar存储在数据段static int staticGlobalVar = 1; // staticGlobalVar也存储在数据段

- 代码段(常量区):存放可执行代码和常量字符串等。常量字符串存储在这里,例如:

cppconst char* pChar3 = "abcd"; // "abcd"存储在代码段

1.2 代码示例分析

下面通过一段代码来具体分析变量在内存中的分布:

cppint globalVar = 1;static int staticGlobalVar = 1;void Test() {static int staticVar = 1;int localVar = 1;int num1[10] = { 1, 2, 3, 4 };char char2[2] = "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);}

-  globalVar 、 staticGlobalVar 、 staticVar 存储在数据段。

-  localVar 、 num1 、 char2 、 pChar3 (指针本身)存储在栈中。

-  *ptr1 、 *ptr2 、 *ptr3 指向的内存区域在堆上。

要点:理解不同类型变量在内存中的存储位置,对于分析程序的内存使用情况和排查问题很有帮助。

易错点:容易混淆局部变量和动态分配内存的生命周期和释放方式。

二、C语言中动态内存管理方式

2.1 malloc

 malloc 函数用于在堆上分配指定大小的内存块,其原型为:

cvoid* malloc(size_t size);

例如,分配一个 int 类型大小的内存空间:

cint* ptr = (int*)malloc(sizeof(int));if (ptr == NULL) {// 处理内存分配失败的情况perror("malloc");return;}// 使用ptrfree(ptr); // 使用完后释放内存

2.2 calloc

calloc 函数用于分配指定数量和大小的内存块,并将其初始化为0,原型为:

cvoid* calloc(size_t num, size_t size);

比如分配4个 int 类型的内存空间:

cint* ptr = (int*)calloc(4, sizeof(int));if (ptr == NULL) {perror("calloc");return;}// 使用ptrfree(ptr);

2.3 realloc

realloc 函数用于调整已分配内存块的大小,原型为:

cvoid* realloc(void* ptr, size_t size);

例如,调整之前分配的内存块大小:

cint* ptr = (int*)malloc(sizeof(int) * 4);if (ptr == NULL) {perror("malloc");return;}int* newPtr = (int*)realloc(ptr, sizeof(int) * 8);if (newPtr == NULL) {perror("realloc");free(ptr);return;}ptr = newPtr; // 更新指针// 使用ptrfree(ptr);

2.4 free

 free 函数用于释放通过 malloc 、 calloc 、 realloc 分配的内存,原型为:

cvoid free(void* ptr);

要点:使用这些函数时,要确保内存分配成功,并且及时释放不再使用的内存,避免内存泄漏。

易错点:

- 对 NULL 指针调用 free 虽然不会出错,但这是不必要的操作,还可能掩盖真正的问题。

- 多次释放同一块内存会导致程序崩溃。

三、C++内存管理方式

3.1 new/delete操作内置类型

在C++中,可以使用 new 和 delete 操作符进行动态内存管理。

- 申请单个 int 类型空间:

cppint* ptr4 = new int;delete ptr4;

- 申请单个 int 类型空间并初始化为10:

cppint* ptr5 = new int(10);delete ptr5;

- 申请10个 int 类型的空间:

cppint* ptr6 = new int[10];delete[] ptr6;

3.2 new/delete操作自定义类型

对于自定义类型(如类), new 会调用构造函数, delete 会调用析构函数。

cppclass A {public:A(int a = 0) : _a(a) {std::cout << "A():" << this << std::endl;}~A() {std::cout << "~A():" << this << std::endl;}private:int _a;};int main() {A* p1 = (A*)malloc(sizeof(A)); // 仅分配内存,未调用构造函数A* p2 = new A(1); // 分配内存并调用构造函数free(p1); // 未调用析构函数delete p2; // 调用析构函数并释放内存return 0;}

要点:使用 new 和 delete 时要注意配对使用,申请和释放单个元素用 new 和 delete ,申请和释放数组用 new[] 和 delete[]  。

易错点:容易忘记使用 delete[] 释放动态数组,导致内存泄漏或程序错误。

四、operator new与operator delete函数

4.1 原理

new 和 delete 是用户进行动态内存申请和释放的操作符,而 operator new 和 operator delete 是系统提供的全局函数。 new 在底层调用 operator new 来申请空间, delete 在底层通过 operator delete 来释放空间。

 operator new 实际通过 malloc 来申请空间,如果 malloc 申请空间成功则直接返回;申请空间失败,尝试执行空间不足应对措施,如果用户未设置该措施则继续申请,否则抛异常。

 operator delete 最终是通过 free 来释放空间。

4.2 代码示例

cpp// operator new的简单实现示意void* operator new(size_t size) {void* p = malloc(size);if (p == NULL) {// 处理内存不足情况,这里简单抛异常throw std::bad_alloc();}return p;}// operator delete的简单实现示意void operator delete(void* p) {if (p != NULL) {free(p);}}

要点:了解 operator new 和 operator delete 的原理,有助于在深入理解 new 和 delete 操作符的底层实现,在自定义内存管理机制时也很有帮助。

易错点:在重载 operator new 和 operator delete 时,要确保实现的正确性,避免引入新的内存问题。

五、new和delete的实现原理

5.1 内置类型

对于内置类型, new 和 malloc , delete 和 free 基本类似。不同的是, new/delete 申请和释放的是单个元素的空间, new[] 和 delete[] 申请的是连续空间,而且 new 在申请空间失败时会抛异常, malloc 会返回 NULL 。

5.2 自定义类型

-  new 的原理:

- 调用 operator new 函数申请空间。

- 在申请的空间上执行构造函数,完成对象的构造。

-  delete 的原理:

- 在空间上执行析构函数,完成对象中资源的清理工作。

- 调用 operator delete 函数释放对象的空间。

-  new T[N] 的原理:

- 调用 operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成N个对象空间的申请。

- 在申请的空间上执行N次构造函数。

-  delete[] 的原理:

- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理。

- 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释放空间。

要点:理解不同情况下 new 和 delete 的实现原理,有助于正确使用它们来管理内存。

易错点:在涉及自定义类型的动态内存管理时,要确保构造函数和析构函数的正确调用,以及内存的正确申请和释放。

六、定位new表达式(placement - new)

6.1 概念

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

6.2 使用格式

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

其中 place_address 必须是一个指针, initializer-list 是类型的初始化列表。

6.3 使用场景

通常配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用定位 new 表达式进行显式调用构造函数进行初始化。

cppclass A {public:A(int a = 0) : _a(a) {std::cout << "A():" << this << std::endl;}~A() {std::cout << "~A():" << this << std::endl;}private:int _a;};int main() {char* buffer = new char[sizeof(A)];A* p = new (buffer) A(1); // 使用定位new表达式在buffer指向的内存上构造A对象p->~A(); // 手动调用析构函数delete[] buffer;return 0;}

要点:掌握定位 new 表达式的使用,可以在特定场景下灵活地进行对象的构造和内存管理。

易错点:使用定位 new 表达式构造对象后,需要手动调用析构函数来清理资源,否则会导致内存泄漏或资源未释放问题。

七、内存泄漏相关问题

7.1 内存泄漏概念

内存泄漏不是指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,从而造成了内存的浪费。

7.2 内存泄漏分类

- 堆内存泄漏(Heap leak):程序执行中通过 malloc 、 calloc 、 realloc 、 new 等从堆中分配的内存,用完后未通过相应的 free 或 delete 删除,导致这部分空间无法再被使用。

- 系统资源泄漏:程序使用系统分配的资源(如套接字、文件描述符、管道等),没有使用对应的函数释放掉,导致系统资源浪费,严重可致系统效能减少、执行不稳定。

7.3 如何检测内存泄漏

- 在Windows下,可以使用 _CrtDumpMemoryLeaks 函数进行简单检测,该函数只报出大概泄漏了多少个字节,没有其他更准确的位置信息。示例如下:

cppint main() {int* p = new int[10];_CrtDumpMemoryLeaks();return 0;}

- 在Linux下,可以使用 valgrind 等工具进行内存泄漏检测。

7.4 如何避免内存泄漏

- 养成良好的编码规范,申请的内存空间及时匹配释放。

- 采用RAII(Resource Acquisition Is Initialization)思想或智能指针来管理资源。

- 一些公司内部规范使用内部实现的私有内存管理库,自带内存泄漏检测功能。

要点:了解内存泄漏的概念、分类、检测和避免方法,有助于编写出更健壮、稳定的程序。

易错点:在复杂程序中,容易忽略对动态分配内存的释放,特别是在异常处理路径中。

八、常见面试题

8.1 malloc/calloc/realloc的区别

-  malloc :分配指定大小的内存块,不初始化内存内容,返回指向分配内存起始地址的指针。

-  calloc :分配指定数量和大小的内存块,并将其初始化为0,返回指向分配内存起始地址的指针。

-  realloc :调整已分配内存块的大小,可能会移动内存块的位置,返回调整后内存块的指针。

8.2 malloc的实现原理

malloc 的实现原理较为复杂,常见的实现方式是通过维护一个空闲内存块链表,当调用 malloc 时,从链表中寻找合适大小的空闲块进行分配,如果没有合适大小的块,可能会进行内存的合并或向操作系统申请更多内存。在glibc中, malloc 的实现涉及到多个数据结构和算法来管理内存,如 arena 、 bin 等。

总结

C/C++内存管理是一个复杂而又关键的知识点,涵盖了内存分布、动态内存管理函数和操作符、内存泄漏等多个方面。通过深入理解和不断实践,才能熟练掌握内存管理技巧,编写出高效、稳定、健壮的程序。希望本文能对大家在C/C++内存管理的学习和实践中有所帮助。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词