广州应用网站设计湖州市建设中心网站
广州应用网站设计,湖州市建设中心网站,如何建设下载网站,网站找人做备案的价格#x1f525;个人主页#xff1a;Quitecoder
#x1f525;专栏#xff1a;c笔记仓 朋友们大家好#xff0c;本篇文章我们详细讲解c中的动态内存管理 目录 1.C/C内存分布2.C语言中动态内存管理方式#xff1a;malloc/calloc/realloc/free3.c内存管理方式3.1new/delete对内…
个人主页Quitecoder
专栏c笔记仓 朋友们大家好本篇文章我们详细讲解c中的动态内存管理 目录 1.C/C内存分布2.C语言中动态内存管理方式malloc/calloc/realloc/free3.c内存管理方式3.1new/delete对内置类型的操作3.1.1抛异常 3.2new/delete对自定义类型的操作 4.operator new与operator delete函数5.new和delete的实现原理6.简单了解定位new表达式(placement-new)7.概念辨析7.1 malloc/free和new/delete的区别7.2 内存泄漏 1.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);
}选择题选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)globalVar在哪里__1__ staticGlobalVar在哪里__2__staticVar在哪里__3__ localVar在哪里__4__num1 在哪里__5__char2在哪里__6__ *char2在哪里_7__pChar3在哪里__8__ *pChar3在哪里__9__ptr1在哪里_10___ *ptr1在哪里__11__我们来依次讨论
globalVar 是全局变量不是静态的所以它存储在数据段(静态区)staticGlobalVar 也是全局变量但它是静态的因此它同样存储在数据段(静态区)staticVar 是函数内的静态变量所以它存储在数据段(静态区)因为它的生命周期贯穿程序的整个执行期localVar 是局部变量存储在栈上num1 是局部变量它是数组存储在栈上char2 是局部变量它是数组首元素的地址存储在栈上*char2即char2数组的内容存储在栈上因为char2本身就在栈上pChar3 是局部指针变量存储在栈上*pChar3 指向的内容即字符串abcd存储在代码段(常量区)ptr1 是局部指针变量存储在栈上*ptr1 指向的内容即通过malloc分配的内存存储在堆上 *char2局部字符数组 当你声明一个局部字符数组并用一个字符串字面量初始化它如char char2[] abcd;时编译器在栈上为数组分配内存然后将字符串字面量的内容包括结尾的\0字符复制到这块内存中。因此char2和它的内容*char2指向的内容都存储在栈上 *pChar3字符串字面量指针 另一方面当你使用指针指向一个字符串字面量如const char* pChar3 abcd;时这个字符串字面量存储在程序的只读数据段或称为代码段、常量区中。pChar3本身作为一个局部指针变量存储在栈上但它指向的字符串“abcd”实际上存储在常量区。这是因为字符串字面量被视为常量数据编译器会将它们放在程序的常量区域内这个区域通常是只读的以防止程序意外修改它的内容。因此尽管pChar3是一个指针存储在栈上但它指向的字符串内容存储在常量区
总结
*char2不在常量区因为char2是局部字符数组其内容直接存储在栈上。*pChar3在常量区因为它指向的是一个字符串字面量字符串字面量被存储在程序的常量区域这部分内存是只读的。
当我们讨论变量存储在哪里时通常涉及到几个关键区域栈Stack、堆Heap、数据段Data Segment又称静态区、和代码段Code Segment又称常量区。每种类型的变量根据其特性和声明周期被存储在这些区域中的相应位置 栈是用于存储局部变量、函数参数等的内存区域。当一个函数被调用时其局部变量和一些书keeping信息被推入栈中当函数执行完成这些信息被从栈上弹出。栈是自动管理的开发者无需手动分配或释放内存。 堆是用于动态内存分配的内存区域。不同于栈开发者需要显式地从堆上分配内存如使用malloc或new并在不再需要时释放这些内存如使用free或delete。 数据段又称为静态区用于存储全局变量、静态变量等。这些变量的生命周期贯穿整个程序执行期因此它们被存储在一个特定的、持久的内存区域中。 代码段又称为常量区用于存储程序的执行代码和常量数据如字符串字面量。这部分内存是只读的用来保证程序代码的安全性
2.C语言中动态内存管理方式malloc/calloc/realloc/free
在C语言中动态内存管理是通过一组标准库函数完成的包括malloc, calloc, realloc, 和 free。这些函数允许程序在运行时动态地分配、调整和释放堆内存这是对于管理变化的数据量和大小特别有用的能力。下面是这些函数的基本用法和它们之间的区别 malloc
用法void* malloc(size_t size);功能分配指定字节数的未初始化内存。它返回一个指向分配的内存的指针。如果分配失败返回NULL。示例int* ptr (int*)malloc(sizeof(int) * 4); 这行代码为4个整数分配了内存 calloc
用法void* calloc(size_t num, size_t size);功能为指定数量的元素分配内存每个元素的大小也在参数中指定并自动初始化所有位为0。如果分配失败返回NULL。示例int* ptr (int*)calloc(4, sizeof(int)); 这行代码为4个整数分配了内存并将它们初始化为0。 realloc
用法void* realloc(void* ptr, size_t size);功能调整之前调用malloc或calloc分配的内存块的大小。如果新的大小大于原始大小可能会移动内存块到新的位置以提供足够的连续空间。如果realloc的第一个参数是NULL它的行为就像malloc。示例ptr (int*)realloc(ptr, sizeof(int) * 8); 这行代码将之前分配的内存大小调整为8个整数的大小。 free
用法void free(void* ptr);功能释放之前通过malloc, calloc, 或 realloc分配的内存。一旦内存被释放那块内存就不能再被访问了。注意尝试释放未经分配的内存块或多次释放同一个内存块是不安全的可能导致未定义行为 注意
在使用这些函数时确保正确处理内存分配失败的情况并在内存不再需要时使用free来避免内存泄露。当使用realloc时如果分配失败原始内存不会被释放。因此建议先将realloc的返回值赋给一个临时指针以检查是否分配成功再重新赋值给原始指针以避免内存泄漏。始终确保只对通过malloc, calloc, 或 realloc分配的指针使用free并且每个分配的内存块只被free一次
3.c内存管理方式 C语言内存管理方式在C中可以继续使用但有些地方就无能为力而且使用起来比较麻烦因此C又提出了自己的内存管理方式通过new和delete操作符进行动态内存管理 3.1new/delete对内置类型的操作
new的基本用法
Type* variable new Type(arguments);Type要分配的对象类型variable指向分配的内存的指针arguments传递给构造函数的参数如果需要的话
示例
int* ptr1 new int; 在堆上分配了一个int大小的内存
int* ptr2 new int[10]; 加上方括号[ ]表示分配了十个int大小的内存
释放
对于ptr我们直接delete
delete ptr1;释放数组对象的内存ptr2,我们需要加上方括号
delete [] ptr2;我们也可以分配内存的同时直接初始化
int* ptr5 new int(5);动态申请一个int类型的空间并初始化为5 我们也可以同时开辟多个空间完成初始化
int* ptr6 new int[10] {1,2,3,4,5};后面的空间默认初始化为零
尽管new和delete提供了对象构造和析构的自动管理但程序员仍然需要负责确保每个用new分配的内存都被对应的delete释放以避免内存泄露与malloc和free一样试图delete一个未经new分配的指针或者对同一个指针执行多次delete都是未定义行为并且可能导致程序崩溃当使用new[]分配数组时必须使用对应的delete[]来释放内存。使用错误的delete形式也是未定义行为
来看下面的代码
struct ListNode
{ListNode* _next;int _val;ListNode(int val):_next(nullptr),_val(val){}
};struct ListNode* CreateListNode(int val)
{struct ListNode* newnode (struct ListNode*)malloc(sizeof(struct ListNode));if (newnode NULL){perror(malloc fail);return NULL;}newnode-_next NULL;newnode-_val val;return newnode;
}这是c语言构造一个节点并完成初始化的过程我们来看c的实现
int main()
{ListNode* node1 new ListNode(1);return 0;
}这行代码自动为ListNode对象分配了内存并调用了其构造函数进行初始化。这种方式更简洁也更安全因为它保证了对象在使用前被正确初始化注意这里ListNode是自定义类型除了开空间还会调用构造函数 只要我们写好构造函数我们发现new的使用是十分方便的 我们来构建一个链表
ListNode* CreateList(int n)
{ListNode head(-1); // 哨兵位ListNode* tail head;int val;printf(请依次输入%d个节点的值, n);for (size_t i 0; i n; i){cin val;tail-_next new ListNode(val);tail tail-_next;}return head._next;
}我们输入五个值1 2 3 4 5 哨兵节点ListNode head(-1);这行代码创建了一个局部的哨兵节点它的值被设为-1这个值通常是任意的因为哨兵节点本身不存储任何有意义的数据。哨兵节点的主要目的是简化在链表头部的插入和删除操作因为你总是有一个非空的节点作为链表的起始点从而避免了处理空链表的特殊情况 最后函数通过return head._next;返回新构建链表的头节点。由于head是一个哨兵节点它的_next成员实际上指向链表的第一个真实节点如果有的话或者是nullptr如果n为0或用户没有输入任何有效数据
3.1.1抛异常
我们不用手动检查new是否开辟成功new失败了会抛出异常
void func()
{int n 1;while (1){int* p new int[1024 * 1024*100];cout n- p endl;n;}
}我们一次申请400M的空间大小 再看c语言版本
void func()
{int n 1;while (1){//int* p new int[1024 * 1024 * 100];int* p (int*)malloc(1024 * 1024 * 400);cout n - p endl;n;}
}开辟失败程序无限循环并返回空
c中的抛异常
try{func();}catch (const exception e){cout e.what() endl;}
这段代码是C中的一个示例展示了如何使用try-catch语句来处理异常。这里的重点是捕获并处理func()函数中可能抛出的异常。如果func()函数执行中出现了问题它将抛出一个异常这个异常会被catch块捕获。捕获到的异常类型为const std::exception这是C标准异常类型的一个基类。在catch块中通过e.what()调用来获取并打印出异常的具体信息 try块在try块中的代码执行时如果发生了异常即代码抛出了异常那么try块中的剩余代码将不会继续执行而是跳转到相应的catch块中处理异常 catch块此代码段用于捕获类型为const std::exception的异常。这意味着它能够捕获任何是std::exception实例或其派生类的异常。通过常量引用捕获异常是一种最佳实践因为这样可以避免异常对象的切片问题并且可以最小化性能开销 const exception e这里声明了一个名为e的引用它引用了被捕获的异常。const限定符表明在catch块中e是不会被修改的 e.what()std::exception及其派生类有一个名为what()的成员函数它返回一个描述异常的空终止字符序列C风格字符串。cout e.what() endl;语句将这个消息打印到标准输出中
后续我们还会遇到这个函数再详细讲解
来看抛异常的结果
3.2new/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* p1 new A(1);delete p1;return 0;
}new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间,还会调用构造函数和析构函数 A* p1 new A(1);
00007FF798AA260B mov ecx,4
00007FF798AA2610 call operator new (07FF798AA104Bh)
00007FF798AA2615 mov qword ptr [rbp108h],rax
00007FF798AA261C cmp qword ptr [rbp108h],0
00007FF798AA2624 je main50h (07FF798AA2640h)
00007FF798AA2626 mov edx,1
00007FF798AA262B mov rcx,qword ptr [rbp108h]
00007FF798AA2632 call A::A (07FF798AA1343h)
00007FF798AA2637 mov qword ptr [rbp138h],rax
00007FF798AA263E jmp main5Bh (07FF798AA264Bh)
00007FF798AA2640 mov qword ptr [rbp138h],0
00007FF798AA264B mov rax,qword ptr [rbp138h]
00007FF798AA2652 mov qword ptr [rbp0E8h],rax
00007FF798AA2659 mov rax,qword ptr [rbp0E8h]
00007FF798AA2660 mov qword ptr [p1],raxnew过程跳转到构造函数 delete调用析构函数
打印结果如下
A():000001DB79796B50
~A():000001DB79796B50我们发现汇编代码中有这一步
00007FF798AA2610 call operator new (07FF798AA104Bh)operator new接下来我们来讲解这一部分
4.operator new与operator delete函数 new和delete是用户进行动态内存申请和释放的操作符operator new 和operator delete是系统提供的全局函数new在底层调用operator new全局函数来申请空间delete在底层通过operator delete全局函数来释放空间 void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{void* p;while ((p malloc(size)) 0)if (_callnewh(size) 0){// 如果申请内存失败了这里会抛出bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);
}operator new该函数实际通过malloc来申请空间当malloc申请空间成功时直接返回申请空间失败尝试执行空间不足应对措施如果改应对措施用户设置了则继续申请否则抛异常。 static const std::bad_alloc nomem;申请失败则会抛异常
void operator delete(void* pUserData)
{_CrtMemBlockHeader* pHead;RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));if (pUserData NULL)return;_mlock(_HEAP_LOCK); /* block other threads */__TRY/* get a pointer to memory block header */pHead pHdr(pUserData);/* verify block type */_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead-nBlockUse));_free_dbg(pUserData, pHead-nBlockUse);__FINALLY_munlock(_HEAP_LOCK); /* release other threads */__END_TRY_FINALLYreturn;
}_free_dbg(pUserData, pHead-nBlockUse);operator delete: 该函数最终是通过free来释放空间的 通过上述两个全局函数的实现知道operator new 实际也是通过malloc来申请空间如果malloc申请空间成功就直接返回否则执行用户提供的空间不足应对措施如果用户提供该措施就继续申请否则就抛异常。operator delete 最终是通过free来释放空间的 我们只需要简单了解一下并不需要深入理解
5.new和delete的实现原理 如果申请的是内置类型的空间new和mallocdelete和free基本类似不同的地方是new/delete申请和释放的是单个元素的空间new[]和delete[]申请的是连续空间而且new在申请空间失败时会抛异常malloc会返回NULL 自定义类型
new的原理 调用operator 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* p1 new A(1);delete p1;return 0;
}delete的原理 在空间上执行析构函数完成对象中资源的清理工作调用operator delete函数释放对象的空间
class Stack
{
public:Stack(){_a (int*)malloc(sizeof(int) * 4);_top 0;_capacity 4;}~Stack(){free(_a);_top _capacity 0;}
private:int* _a;int _top;int _capacity;
};
int main()
{Stack* pst new Stack;delete pst;return 0;
}这里进行了双层嵌套 我们就很清楚的能看到现需要调用析构函数再进行释放 new T[N]的原理 调用operator new[]函数在operator new[]中实际调用operator new函数完成N个对象空间的申请在申请的空间上执行N次构造函数 delete[]的原理 在释放的对象空间上执行N次析构函数完成N个对象中资源的清理调用operator delete[]释放空间实际在operator delete[]中调用operator 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* p1 new A;A* p2 new A[10];delete p1;delete[]p2;return 0;
}在这段代码中p2 是指向由 new A[10] 分配的对象数组的指针。虽然你可能会认为 p2 只需要分配足够存储 10 个 A 类型对象的空间即 10 * sizeof(A)实际上编译器通常会分配额外的空间来存储有关数组本身的信息比如数组的大小。这是因为在执行 delete[] p2; 时系统需要知道要调用多少次析构函数
让我们具体看一下为什么会这样 对象数组的内存分配当你创建一个对象数组时例如 new A[10]C 需要知道在稍后释放数组时应该调用多少次析构函数。为此它可能在分配给数组的内存块中存储一些额外的元数据通常是数组的长度 析构函数调用在使用 delete[] p2; 释放内存时这个额外存储的信息就被用来确保为数组中的每个元素正确调用析构函数 内存布局因此分配给 p2 的内存实际上包含了更多比简单的 10 * sizeof(A) 字节。首先是数组长度的元数据大小取决于系统和编译器紧接着是 10 个 A 类型对象的存储空间 字节大小如果 sizeof(A) 是 4假设 int 类型是 4 字节并且没有类对齐导致的额外空间那么仅对象部分就占用了 40 字节。加上存储数组大小的额外空间总大小就会超过 40 字节 我们再来看内置类型
int* p1new int[10];00007FF7F031206B mov ecx,28h 刚好开辟了四十个字节的空间因为它不需要调用析构函数
6.简单了解定位new表达式(placement-new) 定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象 使用格式 new (place_address) type或者new (place_address) type(initializer-list) place_address必须是一个指针initializer-list是类型的初始化列表
class A
{
public:A(int a 0): _a(a){cout A(): this endl;}~A(){cout ~A(): this endl;}
private:int _a;
};
int main()
{int* p1 new int[10];return 0;
}A* p1 (A*)malloc(sizeof(A));p1现在指向的只不过是与A对象相同大小的一段空间还不能算是一个对象因为构造函数没有执行 new(p1)A;显示调用构造函数对一块已经有的空间的初始化 定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化所以如果是自定义类型的对象需要使用new的定义表达式进行显示调构造函数进行初始化 7.概念辨析
7.1 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在释放空间前会调用析构函数完成空间中资源的清理
7.2 内存泄漏
什么是内存泄漏内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失而是应用程序分配某段内存后因为设计错误失去了对该段内存的控制因而造成了内存的浪费。内存泄漏的危害长期运行的程序出现内存泄漏影响很大如操作系统、后台服务等等出现内存泄漏会导致响应越来越慢最终卡死
分类 堆内存泄漏(Heap leak) 堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放那么以后这部分空间将无法再被使用就会产生Heap Leak 系统资源泄漏 指程序使用系统分配的资源比方套接字、文件描述符、管道等没有使用对应的函数释放掉导致系统资源的浪费严重可导致系统效能减少系统执行不稳定
本节内容到此结束求大家三连啊
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/87379.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!