建设教育局网站硬件价格需要多少钱?,收录网址教程,什么网站可以做网站,社区网站建设策划方案文章目录 1.C/C内存分布2.C语言中动态内存管理方式3.C内存管理方式3.1 new/delete操作内置类型3. 2new/delete操作自定义类型 4. operator new与operator delete函数#xff08;重点#xff09;5. new和delete的实现原理5.1 内置类型5.2 自定义类型5.2.1 自定义类型调用new[]… 文章目录 1.C/C内存分布2.C语言中动态内存管理方式3.C内存管理方式3.1 new/delete操作内置类型3. 2new/delete操作自定义类型 4. operator new与operator delete函数重点5. new和delete的实现原理5.1 内置类型5.2 自定义类型5.2.1 自定义类型调用new[]和delete[]时的崩溃问题 6.定位new表达式了解性内容7. malloc/free和new/delete的区别结语 1.C/C内存分布
在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);
}我们由上往下依次讲解变量在内存的分布
变量名存储类型存储位置globalVar全局变量数据段静态区staticGlobalVar静态全局变量数据段静态区staticVar局部静态变量数据段静态区localVar局部变量栈num1局部数组栈char2局部数组栈*char2数组内第一个元素栈pChar3指向常量字符串的指针变量栈*pChar3常量字符串代码段ptr1指向动态内存的指针变量栈*ptr1动态开辟的空间堆ptr2指向动态内存的指针变量栈*ptr2动态开辟的空间堆ptr3指向动态内存的指针变量栈*ptr3动态开辟的空间堆
这里需要注意的是*char2和*pChar3的区别
*char2是将字符串拷贝到数组里其实数组里面存储的是这些字符的ASCII码值*char2就是取数组的第一个元素既然数组都是在栈上所以身为数组成员的元素也是在栈上的。*Char3则是解引用这个直接指向常量字符串的指针解引用完后是直接得到这个常量的所以是在代码段。 说明 栈又叫堆栈–非静态局部变量/函数参数/返回值等等栈是向下增长的。 内存映射段是高效的I/O映射方式用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存做进程间通信。 堆用于程序运行时动态内存分配堆是可以上增长的。 数据段–存储全局数据和静态数据。 代码段–可执行的代码/只读常量。 2.C语言中动态内存管理方式
C语言是通过malloc / calloc / realloc / free这四个函数来管理的其中前三个函数是负责开辟内存和扩容free是释放开辟的内存动态开辟内存后一定要释放不然会内存泄漏
void Test()
{// 1.malloc/calloc/realloc的区别是什么int* p2 (int*)calloc(4, sizeof(int));int* p3 (int*)realloc(p2, sizeof(int) * 10);// 这里需要free(p2)吗free(p3);
}这里有三个问题 malloc/calloc/realloc的区别是什么这里需要free(p2)吗malloc的实现原理 第一个问题 malloc只会进行开空间。
calloc不仅会开空间还会将空间初始化为零。
realloc是扩容但是realloc延迟是有两种情况的原地扩容和异地扩容如果realloc第一个参数为空那realloc也能发挥malloc的作用详细内容可以看这篇博客C语言【动态内存】
第二个问题 p3是realloc p2后的指针那我们要看realloc扩容的方式 如果是原地扩容那么p3是跟p2一样是指向同一块空间这时候任意free一个就行不可两个都free。 如果是异地扩容那么就会先开辟空间然后将p2的数据拷贝到新开的空间然后自动free掉p2。
由于我们不确定是原地还是异地扩容且这两种都可以free掉p3所以我们不管是原地扩容还是异地扩容free掉p3都是最保险最安全的做法。
第三个问题 可以通过该链接进行学习glibc中malloc实现原理。
3.C内存管理方式
C语言内存管理方式在C中可以继续使用但有些地方就无能为力而且使用起来比较麻烦因此C又提出了自己的内存管理方式通过new和delete操作符进行动态内存管理。
3.1 new/delete操作内置类型
void Test()
{// 动态申请一个int类型的空间int* ptr1 new int;// 动态申请一个int类型的空间并初始化为10int* ptr2 new int(10);// 动态申请10个int类型的空间int* ptr3 new int[10];// 动态申请10个int类型的空间并初始化为0int* ptr4 new int[10] {0};// 动态申请10个int类型的空间并初始化前一半的的元素int* ptr5 new int[10] {1, 2, 3, 4, 5};delete ptr1;delete ptr2;delete[] ptr3;delete[] ptr4;delete[] ptr5;
}int main()
{Test();
}注意申请和释放单个元素的空间使用new和delete操作符申请和释放多个元素的空间要使用new[]和delete[]操作符需要配套来使用。
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()
{// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数//A* p1 (A*)malloc(sizeof(A));A* p2 new A(1);//free(p1);delete p2;// 操作内置类型是几乎是一样的int* p3 (int*)malloc(sizeof(int)); // Cint* p4 new int;free(p3);delete p4;return 0;
}注意new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数
我们在C语言使用malloc等函数的时候需要通过if来判断申请是否成功我们这里并没有判断那就代表new就不会失败吗 其实不是的我们在C动态开辟内存的时候是通过捕捉异常状态来判断是否开辟成功我们平时练习开辟的那点空间是不足以开辟失败的
#includeiostream
using namespace std;int main()
{
捕获异常的写法我们现在只需要知道用法具体我们会在后面详细讲解try{void* p1 new char[1024 * 1024 * 1024];//1Gcout p1 - p1 endl;void* p2 new char[1024 * 1024 * 1024];cout p2 - p2 endl;}catch (const exception e){cout e.what() endl;}return 0;
}我们先来探索下在32位下堆平台的内存有多大每次申请1MB
#includeiostream
using namespace std;void func()
{int n 1;while (1){void* p1 new char[1024 * 1024];// 1Mbcout p1 - n endl;n;}
}int main()
{try{func();}catch (const exception e){cout e.what() endl;}return 0;可以看到在32位环境下堆有1897Mb的内存约1.85G占了接近一半的内存32位下内存为4G -- 2 32 2^{32} 232
接下来我们看下64位环境下能申请多少内存 大约43G64位的虚拟内存大约有160亿G 2 64 2^{64} 264个字节是非常大的。
4. operator new与operator delete函数重点
new和delete是用户进行动态内存申请和释放的操作符operator new 和operator delete是系统提供的全局函数new在底层调用operator new全局函数来申请空间delete在底层通过operator delete全局函数来释放空间。
下面是关于operator new和 operator delete的源码
/*operator new该函数实际通过malloc来申请空间当malloc申请空间成功时直接返回申请空间失败尝试执行空间不足应对措施如果改应对措施用户设置了则继续申请否则抛异常
*/
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);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
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));!!!这个是/* operator datele */的核心!!!_free_dbg(pUserData, pHead-nBlockUse);__FINALLY_munlock(_HEAP_LOCK); /* release other threads */__END_TRY_FINALLYreturn;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)通过上述两个全局函数的实现知道operator new 实际也是通过malloc来申请空间如果malloc申请空间成功就直接返回否则执行用户提供的空间不足应对措施如果用户提供该措施就继续申请否则就抛异常。operator delete 最终是通过free来释放空间的
5. new和delete的实现原理
5.1 内置类型
如果申请的是内置类型的空间new和mallocdelete和free基本类似不同的地方是 new/delete申请和释放的是单个元素的空间new[]和delete[]申请的是连续空间而且new在申请空间失败时会抛异常malloc会返回NULL。
5.2 自定义类型
new的原理 调用operator new函数申请空间在申请的空间上调用构造函数完成对象的初始化 我们可以通过反汇编来看是否如此 #includeiostream
using namespace std;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函数释放空间 new[]的原理 调用operator new[]函数operator new[]实际是在内部调用operator new函数来申请空间在申请的空间上执行N次构造函数来初始化N个对象 delete[]的原理 执行N次析构函数完成对N个对象的资源清理调用operator delete[]释放空间实际也是在里面调用operator detele来释放空间 5.2.1 自定义类型调用new[]和delete[]时的崩溃问题
#includeiostream
using namespace std;class A
{
private:int _a;
};class B
{
public:~B(){}
private:int _b;
};int main()
{//运行不会崩溃A* p1 new A[3];delete p1;//运行会崩溃B* p2 new B[3];delete p2;return 0;
}接下来我们把A部分屏蔽掉单独运行B部分看看会发生什么 我们可以看到明明是一样的操作A部分没有崩溃B部分竟然崩溃了。这是为什么呢
我们现在来分析下原因 原因一 我们之前讲过一个很重要的点申请和释放单个元素的空间使用new和delete操作符申请和释放多个元素的空间要使用new[]和delete[] 操作符需要配套来使用难道是因为我们没有配套使用吗 很显然不是因为我们A部分也没有配套来使用啊所以这个原因不对。
原因二是因为只调用了第一个对象的析构函数但没有调用其他对象的析构所导致的内存泄漏吗这并不是崩溃的原因。 因为内存泄漏这个问题编译器是不会检查的况且我们使用的自定义类型内部都是内置类型也就没有申请空间也就不存在内存泄漏的问题
原因三真正原因是AB部分创建对象时所开辟的字节不同。注意AB类的大小都是一样的他们都只有一个成员变量且都是int类型
我们先看A部分开辟的字节大小 我们可以看到p1所指向的空间的的确确开辟了12个字节每个类占4字节
我们再来看部分开辟的字节大小 我们可以看到明明都是只申请了三个对象而p2所指向的空间竟然多出了4个字节 这是为什么呢
这里直接告诉大家这多开的四个字节是用来存储你new出对象的个数B* p2 new B[3];这多开的四个字节就是用来存储[]的3。 而这个多开的字节是给delete[]用的因为我们在使用delete[]的时候并没有表明有多少个要调用多少次析构所以会往前偏移四个字节来读取对象的个数。
所以我们如果是new[]delete来使用的话会在对象占用空间那里开始释放。
但是我们知道一块连续的内存空间是不可以从中间开始释放的一定要从起点开始释放不然编译器会崩溃掉。
好了我们就找到了崩溃的真正原因我们并没有在起点释放内容。
但现在又有一个问题为什么A部分也是new[]但没有多开这四个字节呢这是因为编译器的优化。
因为我在写A类的时候并没有显示的写析构函数编译器检查了一下A类发现A类不写析构函数也可以因为A也没有申请资源所以就直接一优倒底就不调用析构函数了也就不多开空间来记录个数了。
但是如果我们写了析构函数编译器就不会优化就是多开。 6.定位new表达式了解性内容
定位new表达式就是在已分配的原始内存空间中调用构造函数初始化一个对象。 使用格式 new (place_address) type或者new (place_address) type(initializer-list) place_address必须是一个指针initializer-list是类型的初始化列表 使用场景 定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化所以如果是自定义类型的对象需要使用new的定义表达式进行显示调构造函数进行初始化.
#includeiostream
using namespace std;
class A
{
public:A(int a 0): _a(a){cout A(): this endl;}~A(){cout ~A(): this endl;}
private:int _a;
};// 定位new/replacement new
int main()
{// p1现在指向的只不过是与A对象相同大小的一段空间还不能算是一个对象因为构造函数没有执行A* p1 (A*)malloc(sizeof(A));new(p1)A; // 注意如果A类的构造函数有参数时此处需要传参p1-~A();free(p1);A* p2 (A*)operator new(sizeof(A));new(p2)A(10);p2-~A();operator delete(p2);return 0;
}7. 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在释放空间前会调用析构函数完成空间中资源的清理释放
最后两点是核心区别。
结语
这次的分享就到这里结束了~ 最后感谢您能阅读完此片文章~ 如果您认为这篇文章对你有帮助的话可以用你们的手点一个免费的赞并收藏起来哟~ 如果有任何建议或纠正欢迎在评论区留言~ 也可以前往我的主页看更多好文哦(点击此处跳转到主页)。