网站内链规划,免费推广渠道,灰色网站如何推广,搜索更多网页内容学完了类与对象#xff0c;这节我们来了解一下内存里的那些事 文章目录 一、C/C中的内存分布 1. 常量区#xff08;代码段#xff09; (Text Segment) 2. 静态区#xff08;数据段#xff09; (Data Segment) 3. 堆区 (Heap) 4. 栈区 (Stack) 5. 内存映射区域 (Memory-map… 学完了类与对象这节我们来了解一下内存里的那些事 文章目录 一、C/C中的内存分布 1. 常量区代码段 (Text Segment) 2. 静态区数据段 (Data Segment) 3. 堆区 (Heap) 4. 栈区 (Stack) 5. 内存映射区域 (Memory-mapped Region) 6. 堆栈溢出 内存布局总结 重要细节 二、C/C的内存管理方式 C语言中的内存管理 C中的内存管理 new/delete操作内置类型 new和delete操作自定义类型 operator new与operator delete函数 new/delete的实现原理 对于内置类型 对于自定义类型 定位new表达式(placement-new) 三、malloc/free和new/delete的区别 1. 使用的语言 2. 内存分配和释放方式 3. 类型安全 4. 构造和析构函数 5. 内存分配失败 6. 效率 7. 使用习惯 前言
在C语言解答我们就已经学习了内存里面的那些东西我们也会使用动态内存的一些函数但是那些函数都是在C语言中所使用的现在我们学习C了也要学习一下C中动态内存的一些东西。 一、C/C中的内存分布
在我们学习如果管理内存之前我们首先要知道内存中有什么东西内存中的东西是如何分布的。在 C/C 中程序的内存分布是指程序在运行时操作系统如何将不同类型的数据存储在内存的不同区域。通常程序的内存可以分为以下几个主要部分
1. 常量区代码段 (Text Segment)
内容包含程序的可执行代码。特点此区域通常是只读的防止程序意外地修改其指令。操作系统会将其加载到内存中并且可能会共享同一程序实例的多个进程。
2. 静态区数据段 (Data Segment)
内容用于存储程序中的全局变量、静态变量即全程存在的变量以及它们的初始值。分为两部分 已初始化数据段存储已初始化的全局和静态变量。未初始化数据段 (BSS段)存储未初始化的全局和静态变量。BSS段的变量会在程序启动时被操作系统自动初始化为 0。
3. 堆区 (Heap)
内容用于动态分配内存。由程序员通过 malloc、calloc、realloc 等函数在 C 语言中或 new 操作符在 C 中分配内存。特点堆是一个可以动态扩展的区域程序员需要手动管理内存的分配与释放通过 free、delete 等。如果堆内存不及时释放会导致内存泄漏。
4. 栈区 (Stack)
内容用于存储局部变量、函数参数、返回地址等。每当调用一个函数时栈会为该函数分配空间当函数执行完毕时空间被释放。特点 先进后出栈的特点是后进先出LIFO。每次函数调用会压栈函数返回会出栈。局部变量存储局部变量通常存储在栈上当函数退出时它们的内存会自动释放。
5. 内存映射区域 (Memory-mapped Region)
内容主要用于加载共享库、内存映射文件等。这个区域由操作系统管理可以包括动态链接库、共享内存、I/O 映射等。特点这些区域的内容通常由操作系统管理和控制程序可以直接访问但不直接由程序员管理。
6. 堆栈溢出
内容当栈的空间不足时可能发生栈溢出。一般来说栈空间的大小是有限的。表现栈溢出可能导致程序崩溃常见原因包括无限递归或者分配过多的局部变量。 内存布局总结
区域内容生命周期管理方式栈区局部变量、函数参数、返回地址函数调用时分配调用结束时销毁自动管理堆区动态分配的内存由程序员手动管理程序员手动管理数据区全局变量、静态变量、常量程序运行期间持续存在自动管理文本区程序的代码机器码程序运行时一直存在操作系统加载
内存布局大致如下所示
------------------------ 高地址
| 内存映射区域 |
------------------------
| 堆 |
------------------------
| BSS段未初始化数据 |
------------------------
| 数据段已初始化数据|
------------------------
| 代码段Text段 |
------------------------ 低地址
| 栈 |
------------------------重要细节
栈和堆的增长方向通常情况下栈的增长方向是向下即地址从高到低而堆的增长方向是向上即地址从低到高。因此当栈和堆相遇时可能会导致“栈溢出”或“堆溢出”。内存管理在堆上分配的内存需要程序员显式地管理在栈上分配的内存由编译器自动管理。
光说概念理念啥的还是太悬了我们还是用具体实例来看看吧 二、C/C的内存管理方式
C语言中的内存管理
首先我们先来回顾一下我们之前所学习的C语言中的动态内存管理方式malloc,calloc,realloc,free. //1.malloc//void *malloc(size_t size);//size表示的是我们要申请空间中的元素的大小由于malloc函数在库中的数据类型是void*所以我们每次使用都要进行强转int* p1 (int*)malloc(sizeof(int));//2.calloc//void* calloc(size_t num, size_t size); //num表示要分配的元素个数size表示的是每个元素的大小由于calloc函数在库中的数据类型是void*所以我们每次使用都要进行强转int* p2 (int*)calloc(4, sizeof(int));//3.realloc//void* realloc(void* ptr, size_t new_size);//ptr指向的是已经分配内存的空间地址new_size表示要重新分配的内存大小单位是字节同样地它每次使用时也要使用强转int* p3 (int*)realloc(p2, sizeof(int)*2);//4.free//void free(void *ptr);//ptr表示的是想要释放的内存空间地址直接传递一个指针名字即可free(p1);free(p2);free(p3);
我们从上面的代码和注释我们可以了解到上面三种函数都是申请空间的函数最后一种函数是用来对动态申请的内存进行释放最后一个函数在动态内存管理中是一个不可或缺的如果我们只是一味地去申请空间而不去释放空间就很容易造成内存泄漏的问题。
现在我们对上面那三种动态申请内存的函数malloc/calloc/reaclloc进行一下辨析。我们只从单词拼写来看上面三个单词是十分相似的仅个别单词的差别但是它们的作用大同小异。malloc函数是从堆区主动为对象分配一块内存连续的空间并返回该内存空间的起始地址。使用malloc函数动态申请的空间是不会进行初始化的也就是说它所申请的空间里的内容是一个随机值使用malloc函数时我们要始终记得NULL检查防止返回一个NULL指针造成申请空间失败我们却发现不了。calloc函数也是一个动态内存申请的函数它的功能和malloc函数是十分相似的但是它有两点不同1.它会指定动态申请的元素个数2.它会将动态申请的内容全部初始化为0。有时候在某些情况calloc函数进行动态内存申请还是很好用的。最后是realloc函数这个函数与前面那两个函数的功能有所差异前面两个函数是直接申请空间而这个函数是为一个对象重新申请并分配空间。reaclloc可以扩展或缩小已分配内存的大小。如果扩展内存原有数据保持不变新增内存会被清零如果缩小内存数据保持不变。当 reaclloc成功时它可能会在内存的不同位置分配新的空间因此必须将返回的指针保存到一个新的变量中否则原指针可能会丢失。 C中的内存管理
C语言的内存管理方式在C中其实也是可以用的但是在某些方面使用起来有点麻烦于是C就使用自己内存管理的方式使用new/delete操作符进行内存管理。
new/delete操作内置类型 // 动态申请一个int类型的空间int* ptr4 new int;// 动态申请一个int类型的空间并初始化为10int* ptr5 new int(10);// 动态申请10个int类型的空间int* ptr6 new int[10];//动态申请3个int类型的空间并进行初始化int* ptr7 new int[3] {1, 2, 3};delete ptr4;delete ptr5;delete[] ptr6;delete[]ptr7;我们从上面的代码可以看到我们使用C中的new操作符只要new 数据类型参数就可以动态申请一个指定类型的空间并且可以帮我们初始化为任意值若要初始化一个空间的内容我们在内传递我们要初始化的值若要初始化一个数组空间的内容我们在{ }内传递我们想要初始化的值。对比于C语言中的malloc/calloc它们书写的方式麻烦需要进行强转给其传递要开辟的空间字节大小还不能够随意初始化为自己想要的值C中的new操作符就清楚明白多了。至于delete操作符它对于一个空间的释放以及一个数组空间的释放是有一些差别的希望我们能够注意。
new和delete操作自定义类型 class A
{
public://写了A类的构造函数带缺省参数A(int a10 ): _a(a){cout A(): this endl;}//A类的析构函数~A(){cout ~A(): this endl;}
private:int _a;
};
int main()
{// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间//使用自定义类型来动态申请内存malloc,new都可以申请。但是malloc只能够开空间new不仅开空间而且还会调用A类的构造函数A* p1 (A*)malloc(sizeof(A));A* p2 new A(1);free(p1);delete p2;cout endl;// 内置类型是几乎是一样的因为内置类型没有构造函数只要开辟空间即可int* p3 (int*)malloc(sizeof(int)); // int* p4 new int;free(p3);delete p4;cout endl;//同样地使用自定义类型动态申请数组内存空间new不仅开空间还会调用构造函数中参数的缺省值进行初始化如果构造函数中没有默认的缺省值则无法进行初始化A* p5 (A*)malloc(sizeof(A) * 10);A* p6 new A[10];free(p5);delete[] p6;return 0;
} 由上面的代码以及运行结果我们可以看出来对于那些自定义类型的动态内存管理malloc/new,free/delete 都是可以的但是对于那些自定义类型我们在之前就已经学习过了类中有属于自己的默认构造函数/析构函数malloc/free虽然可以申请释放空间但是它们不能够调用到自定义类型的构造/析构函数。而在C中那些研发出new/delete的大佬们明显发现了这件事情对于一个自定义类型怎么能够不调用它们的构造函数与析构函数呢于是它们就将这些功能打包给了new/delete了。
operator new与operator delete函数
我们在上面已经说过了new/delete就是malloc/free的一个升级版那么它们为什么如此相像呢学到这里不知道你们有没有这样的疑问。难道它们都是为了内存管理C研发者为了图简单就照着malloc/free随便设计两个操作符就当作C专属的呢。其实new/delete的底层就是malloc/free。new/delete它们两个是操作符它们是对一个变量进行一个操作真正发挥作用的是在编译器里面的两个全局函数operator new和operator delete。每次我们使用操作符new/delete它们就悄悄调用上面两个函数。而这两个函数的本质就是malloc/free在C中将其进行封装变成了两个新的函数但是还是有一点点的小差别的malloc如果申请空间失败就会返回NULL而operator new 申请空间失败就会抛出异常而这也是面向对象语言的一个经典特点。上面这两个函数只是实现开辟空间与释放空间的功能并没有调用构造函数/析构函数的功能。
/*
*new/delete 是两个操作符它们是通过调用全局域中的两个函数operator new / operator delete 来进行动态内存管理的
*而上面那两个函数本身又是我们之前C语言中的malloc与free的一个封装展现那两个函数单独使用和malloc与free的功能基本差不多
* 不过operator delete在内存释放失败时会抛出异常而free则会返回一个NULL指针这是一个差异
* new: 1.调用operator new来动态申请空间 2.调用构造函数进行初始化
* delete:1.调用析构函数完成对象内存中资源的清理 2.调用operator delete来动态释放空间
* 至于new T[N] , delete[]就是上面那个原理重复N次
*/class A
{
public:A(int a 10):_a(a){cout A(int a ): this endl;}~A(){cout ~A(): this endl;}private:int _a;
};int main()
{A* p1 new A(10);p1-~A();cout endl;// 这里我们调用operator new 即调用malloc, 因此我们的使用方式也和malloc一样要先进行强转然后在传要创建空间的大小A* p3 (A*)operator new (sizeof(A));new(p3)A;//这一步是定位new表达式我们是为了手动调出p3的构造函数//上面的两步合在一块就是一个new所实现的功能了p3-~A();//这是手动调用析构函数operator delete(p3);cout endl;A* p2 new A;p2-~A();return 0;
}从上面的代码我们可以看出来单单使用operator new/operator delete仅仅是起到malloc/free的功能如果想要调用构造函数/析构函数就需要我们自己手动去调用了。所以我们就索性直接使用new/delete这两个操作符想要的功能它们都会实现。
new/delete的实现原理
对于内置类型
它们的实现原理和malloc/free一样虽然new/delete调用的是上面那两个函数但是那两个函数的本质还是malloc/free。不同的是new/delete类型是申请一个类型的内存空间new[]/delete[]是申请连续的内存空间malloc申请失败时会返回NULL而new申请失败时则会抛出异常。
对于自定义类型
new的原理1.调用operator new函数来动态申请内存空间2.调用构造函数进行初始化。
delete的原理1.调用析构函数对对象资源的清理2.调用operator delete函数来释放内存空间。
newT[N]的原理1.调用operator new[]函数的调用在operator new[]中实际调用operator new N次完成对N个对象的空间申请2.调用N次构造函数进行对N个对象进行初始化
delete[ ]的原理1.在释放空间上进行N次析构函数的调用完成对N个对象的资源清理2.调用operator delete[ ]函数来释放内存实际上是调用operator delete来释放内存。 定位new表达式(placement-new)
placement new 是 C 中的一种特殊形式的 new 表达式它允许用户在指定的内存位置上构造对象而不是在堆上动态分配内存。与常规的 new 表达式不同placement new 不会分配内存而是将对象构造在一个已经存在的内存区域中。
语法
new (pointer) Type(args);pointer 是指向预分配内存的指针必须传递指针。Type 是要构造的对象类型。args 是传递给构造函数的参数即类型的初始化列表
使用场景 定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化所以如果是自定义类型的对象需要使用new的定义表达式进行显示调构造函数进行初始化。
int main()
{char* buffer[sizeof(int)]; // 预分配一块内存,给这个字符数组分配整型字节的大小空间// 在 buffer 上使用placement new构造一个int对象int* ptr new (buffer) int(42); // 在buffer的位置上构造int对象并初始化为42buffer是数组名也是数组首地址即指针cout *ptr endl; // 输出42return 0;
}同样地对于自定义类型的变量使用定位new它同样能够发挥new操作符的作用调用构造函数初始化申请空间但是它是指定内存空间进行分配内存空间并不是从堆中分配内存空间这就是定位new的功能。
三、malloc/free和new/delete的区别
1. 使用的语言
malloc 和 free 是 C 语言中的内存管理函数。new 和 delete 是 C 中的内存管理操作符。
2. 内存分配和释放方式
malloc用来从堆中分配一块指定大小的内存。返回的是一个 void* 指针需要进行类型转换才能使用。它只关心分配的字节数不会调用构造函数。free用来释放 malloc 分配的内存块它接受一个指针释放对应的内存区域。new用来为对象分配内存并调用构造函数。它不仅分配内存还返回一个指向已构造对象的指针。delete用来释放通过 new 分配的内存并自动调用对象的析构函数。
3. 类型安全
malloc/freemalloc 返回的是 void*需要强制转换为目标类型的指针存在类型不安全的风险。free 的参数是 void*可以接收任何类型的指针。new/deletenew 和 delete 是类型安全的new 返回所需类型的指针不需要类型转换。delete 的参数也是相应类型的指针。
4. 构造和析构函数
malloc/freemalloc 只是简单地分配内存不会调用对象的构造函数。free 也不会调用对象的析构函数。new/deletenew 会调用对象的构造函数delete 会调用析构函数确保对象的资源能够被正确清理。
5. 内存分配失败
malloc如果 malloc 无法分配所需的内存它会返回 NULL。newnew 如果无法分配内存会抛出 std::bad_alloc 异常除非使用 new(std::nothrow)此时会返回 nullptr。
6. 效率
malloc/free在 C 语言中malloc 和 free 主要用于手动管理内存不会涉及对象的构造和析构因此相对较低级和直接。new/deletenew 和 delete 由 C 编译器实现通常会做额外的检查比如调用构造/析构函数、内存池管理等可能比 malloc 和 free 稍微慢一些但它们与对象的生命周期管理是紧密结合的。
7. 使用习惯
malloc/free这些函数更常见于 C 语言中或者在与 C 兼容的 C 代码中使用。new/delete这两个操作符是 C 推荐的内存管理方式它们与 C 的面向对象特性如构造、析构兼容。