个人主题网站设计论文,jquery电子商务网站模板,wordpress子页面内容,南宁网站建设产品目录内存布局思维导图1.C/C内存分布数据段#xff1a;栈#xff1a;代码段#xff1a;堆:2.C语言中动态内存管理方式3.C内存管理方式3.1new/delete操作内置类型3.2new和delete操作自定义类型4.operator new 与 operator delete函数5.new和delete的实现原理5.1内置类型5.2自定…
目录内存布局思维导图1.C/C内存分布数据段栈代码段堆:2.C语言中动态内存管理方式3.C内存管理方式3.1new/delete操作内置类型3.2new和delete操作自定义类型4.operator new 与 operator delete函数5.new和delete的实现原理5.1内置类型5.2自定义类型6.必须匹配的操作符6.1内置类型6.2自定义类型new -- freenew[]--free/new[]--delete7.定位new表达式7.1使用格式7.2使用场景7.3内存池8.面试题malloc/free和new/delete的区别9.总结和博客思维导图内存布局思维导图 上图为内存布局的思维导图有关整篇博客的思维导图放在的最后的总结部分
1.C/C内存分布
我们对比图片观察不同的数据存放在那个内存空间的那一块(对C语言内存管理不熟悉可查看该篇博客【C语言】内存管理) 栈 又叫堆栈–非静态局部变量/函数参数/返回值等等栈是向下增长的。内存映射段 是高效的I / O映射方式用于装载一个共享的动态内存库。用户可使用系统接口创建共享的共享内存做进程通信。堆 用于程序运行时动态内存分配堆是可以上增长的。数据段 存储全局数据和静态数据。代码段 可执行的代码/只读常量
接着我们对上图的代码分布情况做出如下解释(上图的代码放在解释后有需要的可以自己复制运行一下)
数据段
全局变量static修饰创建的静态变量一定存放在数据段存放静态数据和全局数据的地方这个没有异议。
栈
localNum变量num1数组 正常在栈上开的空间创建也很好理解。
至于char2、pChar3、ptr1、ptr2和ptr3 我们需要明白一点不论是数组还是指针不论接收的是常量字符串还是程序员自己开辟的空间的地址它们终究是在函数内是在栈上创建的变量用来存放数据罢了它们作为变量的地址就在栈上。
代码段
在C语言中我们就应该知道使用双引号括起来的数据为常量字符串这样的字符串为只读常量不能修改存放在代码段常量区这是规定好的我们理解记忆即可但其中有一些比较迷惑人的地方需要解释一下 使用数组和指针的形式接收常量字符串结果是不同的。 使用数组接收 如上图的char char2[]abcd;常量字符串存放在代码段在赋值时char2在栈上开辟一个相同大小的数组通过拷贝将每个字符存放在数组中(包含最后的’\0’终止字符)所以我们可以修改数组char2中的数据。 使用指针接收 C 如上图的const char* pChar3 abcd;这个就要好理解了就是简单的将常量字符串的首地址存入指针变量pChar而已所以pChar3的地址是在栈上而指针变量pChar3所存的地址是在代码段中。因为常量字符串不能修改所以在C中不允许权限发生改变必须使用const关键字修饰指针。 C 在C语言中使用指针接收字符串常量则可以不使用const修饰指针甚至我们可以使用指针来修改常量字符串编译器不会报错但这样的程序是运行不了的程序在执行到常量字符串修改哪一行就会停下(如下图)所以我们用C语言遇到这种情况最好还是使用const修饰。
堆:
程序员使用malloc、calloc、realloc 开辟的空间必然是放在堆上的这样的空间程序员自己申请也需要自己使用free 释放。空间在堆上开辟好后分别使用指针接收这些空间的首地址所以接收这些地址的指针变量是在栈上开辟但它们所存储的地址却是在堆上。
代码如下
int globalNum 1;
static int staticGlobalNum 1;int main()
{static int staticNum 1;int localNum 1;int num1[10] { 1,2,3,4 };char char2[] abcd;const char* pChar3 abcd;int* ptr1 (int*)malloc(sizeof(int));int* ptr2 (int*)calloc(4, sizeof(int));int* ptr3 (int*)realloc(ptr2,sizeof(int) * 4);free(ptr1);free(ptr3);return 0;
}2.C语言中动态内存管理方式
C语言动态开辟内存的方式即为malloc、calloc和realloc最后由free释放开辟的空间这是学习C必须要掌握的对象而面试时也会经常问道相关的问题我们通过下面两道面试题在来看一下
1.malloc/calloc/realloc的区别 malloc向堆区申请空间参数为申请内存的大小calloc向堆区申请空间第一个参数为申请空间的每一个字节的初始值第二个参数为申请空间的大小realloc向堆区申请空间第一个参数为要调整的地址第二个参数为调整后新的空间的大小若第一个参数为空则realloc和malloc相同。向堆申请的空间必须使用free释放防止内存泄漏。 2.malloc的实现原理
GLibc堆利用入门-机制介绍
比较复杂建议结合操作系统一起学习
3.C内存管理方式 C语言管理内存的方法(malloc、calloc、realloc、free)在C中可以继续使用但有些地方就无能为力而且使用起来比较麻烦比如说自定义类型因此C提出了自己的内存管理方式通过new和delete操作符进行内存管理。 3.1new/delete操作内置类型
我们以int类型为类 动态申请一个int类型的空间 int* ptr1 new int;//申请空间
delete ptr1;//释放空间直接new后跟类型即可向堆申请空间。 动态申请一个int类型的空间并初始化为8 int* ptr2 new int(10);//申请空间
delete ptr2;//释放空间在类型后加小括号在其内填写需要初始的值 若小括号内不添加任何值默认初始化为0 动态申请10个int类型的空间 int* ptr3 new int[10];//申请空间
delete[] ptr3;//释放空间在类型后加中括号其内填需要申请的同类型空间的个数 动态申请10个int连续的空间并初始化 在中括号后加小括号在其内不添加任何值默认初始化为0 int* ptr4_1 new int[10]();//申请空间
delete[] ptr4_1;//释放空间在中括号后加大括号在其内填写数组的值不足的部分默认为0 int* ptr4_2 new int[10] {1,2,3,4};//申请空间
delete[] ptr4_2;//释放空间注意申请和释放单个元素的空间使用new和delete操作符申请和释放连续的空间使用new[]和delete[]需要匹配起来使用不能乱用。原因请看 6.必须匹配的操作符
通过下面代码查看创建空间是否成功
int main()
{int* ptr1 new int;int* ptr2_1 new int(10);int* ptr2_2 new int();int* ptr3 new int[4];int* ptr4_1 new int[4]();int* ptr4_2 new int[4]{ 1,2 };printf(ptr1:%p %d\n, ptr1, *ptr1);printf(ptr2_1:%p %d\n, ptr2_1,*ptr2_1);printf(ptr2_2:%p %d\n, ptr2_2, *ptr2_2);for (int i 0; i 4; i){printf(ptr3:%p %d\n, ptr3[i], ptr3[i]);}for (int i 0; i 4; i){printf(ptr4_1:%p %d\n, ptr4_1[i], ptr4_1[i]);}for (int i 0; i 4; i){printf(ptr4_2:%p %d\n, ptr4_2[i], ptr4_2[i]);}delete[] ptr4_1;delete[] ptr4_2;delete[] ptr3;delete ptr2_1;delete ptr2_2;delete ptr1;return 0;
}3.2new和delete操作自定义类型
new 和 delete操作自定义类型和操作内置类型相同。
new/delete 和malloc/free对于自定义类型时不同的是申请空间时new会调用构造函数释放空间时delete会调用析构函数
class A
{
public:A(int a 0){_a a;cout A(int a 0) _a endl;}~A(){cout ~A() endl;}
private:int _a;
};int main()
{A* ptr1 new A(1);delete ptr1;A* ptr2 new A;delete ptr2;A* ptr3 new A[4];//调用四次构造函数delete[] ptr3;//释放四次构造函数A* ptr4 new A[4]{ 1,2,3,4 };//为每次调用的构造函数传值delete[] ptr4;return 0;
}对于malloc和free什么都不会调用
class A
{
public:A(int a 0){_a a;cout A(int a 0) _a endl;}~A(){cout ~A() endl;}
private:int _a;
};int main()
{A* ptr1 (A*)malloc(sizeof(A));if (ptr1 nullptr){perror(malloc fail!);exit(-1);}free(ptr1);A* ptr2 (A*)malloc(sizeof(A)*4);if (ptr1 nullptr){perror(malloc fail!);exit(-1);}free(ptr2);return 0;
}优势
更简洁申请自定义类型的空间时new会调用构造函数delete会调用析构函数而malloc它们三个不会new申请空间失败会抛出异常而malloc需要自己判断是否申请成功(一些严格的编译器必须要判断)
4.operator new 与 operator delete函数 new和delete 是用户进行动态内存申请和释放的操作符operator new 和 operator delete 是系统提供的全局函数 new在底层调用operator new 全局函数来申请空间delete在底层通过operator delete 全局函数来释放空间。
我们编写如下代码通过汇编查看
class A
{
public:A(int a 0){_a a;cout A(int a 0) _a endl;}~A(){cout ~A() endl;}
private:int _a;
};int main()
{A* ptr new A;delete ptr;return 0;
}根据这些我们知道new在申请空间时是通过operator new 申请之后调用构造函数初始化。
至于delete我使用的VS2019不方便查看大家要明白它底层调用的是operator delete 还有下面的一点。
注意对于自定义类型的对象一定是先调用析构函数清理掉对象的数据之后再使用operator delete 释放空间否则空间之间释放析构函数就没办法调用了。内置类型没有这个需求直接释放
接着我们看一下operator new 和 operator delete 是则么实现的:
operator new:
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid* p;while ((p malloc(size)) 0)if (_callnewh(size) 0){// report no memory// 如果申请内存失败了这里会抛出bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);
}观察第5行operator new 实际上是通过malloc 来申请空间当malloc 申请空间成功时直接返回当申请失败且用户设置了空间不足应当措施则尝试执行该措施继续申请。若没有设置则抛出异常。
operator delete
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)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;
}观察第4行和18行operator delete 实际上是通过free 释放空间的 我们可以将operator new 和 operator delete 看作是对malloc和free 的封装之所以要这么做是因为C是面向对象的语言当申请空间发生错误时不能像C那样去处理问题直接返回个NULL需要让用户更直观的看到问题的出现需要增加异常 这个功能如果了解一些Java的知识想必对异常并不陌生这正是面向对象的特点。 5.new和delete的实现原理
5.1内置类型
申请内置类型的空间new和mallocdelete和free基本类似 不同的地方有以下两点
new/delete申请和释放单个元素的空间new[]/delete[]申请和释放的是连续的空间malloc和free不用区分这些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 new[]相同operator delete[]中调用operator delete来释放空间。
6.必须匹配的操作符
我们使用操作符申请空间时必须匹配使用malloc/free、new/delete、new[]/delete[]
分别看一下不匹配会发生什么
6.1内置类型
int main()
{int* ptr1 new int;free(ptr1);int* ptr2 new int[10];free(ptr2);return 0;
}我们执行上述代码发现编译器并不会报错因为new 也是通过malloc 来申请空间通过free 释放的对于内置类型我们直接使用free 释放是没有问题的。
6.2自定义类型
定义如下类之后的代码会用下面的类来创建对象申请空间没有用到的地方会说明
class A
{
public:A(int a 0){_a a;cout A(int a 0) _a endl;}~A(){cout ~A() endl;}
private:int _a;
};new – free
int main()
{A* ptr1 new A;free(ptr1);return 0;
}我们看到这样的代码执行起来编译器也是不会报错的只是没有执行析构函数这种情况就要分类讨论了 无需要释放的资源 就像这个A类它执行不执行析构函数都是无所谓的因为它对象内没有在额外申请空间我们只需要释放掉它new出来的即可这一点free是可以做到的。 有需要释放的资源 我们将上面的代码修改一下在来运行 class B
{
public:B(int a 4){_pa new int[a];cout B(int a 0) endl;}~B(){delete[] _pa;cout ~A() endl;}
private:int* _pa;
};int main()
{B* ptr new B;free(ptr);return 0;
}观察上面的代码我们free 只是将ptr 指向的空间释放而对象ptr 内 _pa 指向的空间却没有释放这就造成内存泄漏 这是很严重的事情而C/C中我们自己申请的内存需要我们自己释放编译器是不会管的也不会检查出内存泄漏 的依然会正常运行。 所以delete 的顺序为先调用析构函数释放对象内的资源之后operator delete 释放掉对象的空间。 关于内存泄漏的危害我举个例子如果一个程序一直在运行而它存在内存泄漏那它会一直消耗空间直到程序无法运行这是很严重的问题。 我们使用下面的代码来检测一个程序可以开辟的最大空间我使用的是VS2019 int main()
{int size 0;while (1){int* ptr (int*)malloc(sizeof(int)*2*1024);size 1;//每次开辟1KB的空间if (ptr nullptr){break;}}cout size / 1024 MB endl;return 0;
}结论 new和malloc系列底层实现机制有关联交叉。不匹配使用可能出现问题也可能没有问题所以大家一定要匹配使用。
拓展
如果对java有一定了解应该知道java是没有释放内存这一说的因为它有一个垃圾回收的机制叫做GC当我们new一个对象GC就会记录下来使用过后需要释放该对象时JVMjava虚拟机 就会实现这一机制释放空间。
至于C为什么不搞这样的机制因为C/C是极度追求性能的GC 这样的机制一定程度上会对性能造成影响所以C需要程序员自己释放以此提高性能。
从学习语言的角度有GC也并不就是一件好事虽然用起来方便但学习Java的每个人都要懂得GC 这个复杂的机制。
new[]–free/new[]–delete
观察下面两段代码及运行结果
代码1
int main()
{A* ptr new A[4];free(ptr);return 0;
}编译器报错无法正常运行。
代码2
int main()
{A* ptr new A[4];delete ptr;return 0;
}编译器报错无法正常运行。
这两段代码报错的原因和析构函数无关就像上一段讲的析构函数哪怕不调用最坏是内存泄漏编译还是可以执行的不会突然报错报错的原因在于new 开辟连续空间的机制
我们先来看正确的写法 A* ptr new A[4];delete[] ptr;使用new申请连续的空间时我们在类型后的中括号内填入了数字4告诉编译器需要申请4个A类型的空间需要调用4次构造函数而在delete时却没有告诉编译器到底调用几次析构函数释放多少空间编译器会记录我们使用malloc为内置类型开辟连续的空间时也没有告诉编译器需要free多大的空间就是证明那编译器是怎么知道要调用几次析构函数的 在new开辟空间时编译器会在开辟的空间起始地址处向前在开辟4个字节的空间用来存放需要调用的析构函数个数只用来存储调用几次析构释放空间的大小编译器会使用其他办法记录若释放空间时写法为delete[]释放空间会包含这4个字节也就是释放空间时指针指向的起始位置为这四个字节的首地址 也就知道了需要调用几个析构函数若写法为delete/free则不会去理会起始位置前的这四个字节指针指向的位置就是申请的空间的起始位置 只是按照步骤走下去delete调用一次析构然后释放空间free直接释放空间那4个存储数字的字节被保留下来释放空间的指针位置不对因此导致编译器报错。 一般程序崩溃都是指针指向的问题这个就是指向的初始位置不对像内存泄漏是不会崩溃的。
这里还要说明一点我们在定义A类时我们自己写了析构函数若我们自己没写则使用new[]/fee、new[]/delete 是不会报错的
class A
{
public:A(int a 0){_a a;cout A(int a 0) _a endl;}//~A()//{// cout ~A() endl;//}
private:int _a;
};
int main()
{A* ptr1 new A[4];free(ptr1);A* ptr2 new A[4];delete ptr2;return 0;
}如上当我们将析构函数注释后类内就只有编译器自己的默认析构函数这时编译器会认为没有调用析构函数的必要 也就不需要在开辟4个字节来存储需要调用析构函数的次数编译器正常运行。 注意 再次强调因为C的语法和底层比较复杂哪怕我们知道了怎么开辟和释放空间编译器不会报错但终究会造成问题如自己的使用失误误导他人等等我们还是坚持遵守C的语法配套使用malloc/free、new/delete、new[]/delete[] 不交叉使用。
7.定位new表达式 定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。 7.1使用格式
new(place_address)type 或 new(place_address)type(initializer_list)
place_address必须时一个指针initializer_list是类型的初始化列表
7.2使用场景
如下我们使用malloc来申请一块自定义类型的空间申请后这块空间是没有办法自动调用构造函数的只能通过定位new 来调用构造函数初始化释放时则直接调用析构函数即可。
class A
{
public:A(int a 0){_a a;cout A(int a 0) _a endl;}~A(){cout ~A() endl;}
private:int _a;
};int main()
{A* ptr (A*)malloc(sizeof(A));new(ptr)A(1);//定位new传参//new(ptr)A;//定位new不传参ptr-~A();//调用析构函数return 0;
}想必大家都有疑问为什么要写的这么复杂直接new一个对象出来不就好了 A* ptr new A;delete ptr;凡是存在必然会有它一定的道理我们一般使用new是向操作系统的堆来申请空间但操作系统分配空间效率上会有些慢所以有些地方如果需要频繁的申请内存就会有一个内存池用来存储需要分配的空间并且效率要高于操作系统同时内存池分配出的内存没有初始化如果是自定义类型的对象 需要使用new的定义表达式进行初始化。 7.3内存池
接着我们来看一下内存池的工作流程 当内存池内有内存时我们直接向其申请它就会分配给我们空间 当内存池内没有内存时我们直接向其申请它会先和操作系统申请告诉操作系统自己没有内存了操作系统就会返回其大量内存空间使其短时间内不会在缺少内存防止效率下滑。
8.面试题
下面是一道面试题不建议大家去背做到理解记忆最好
malloc/free和new/delete的区别
malloc和free是函数new和delete是操作符malloc申请的空间不会初始化new可以初始化malloc申请空间时需要手动计算空间的大小并传递new只需要在其后跟上空间的类型即可如果是多个对象[]中指定对象个数即可。malloc的返回值为void*在使用时必须强转new不需要因为new后跟的是空间的类型。malloc申请空间失败时返回的是NULL因此使用必须判空new不需要但是new需要捕获异常。申请自定义类型对象时malloc/free只会开辟空间不会调用构造函数与析构函数而new申请空间后会调用构造函数完成对象的初始化delete在释放空间前会调用析构函数完成空间中资源的清理。
9.总结和博客思维导图
对于内存管理C/C几乎类似只是C在C的基础上做出了更适合面向对象的调整
增加了new/delete针对自定义类型更好的调用构造和析构函数