内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内存管理在C++中无处不在,内存泄漏几乎在每个C++程序中都会发生,因此要想成为C++高手,内存管理一关是必须要过的,除非放弃C++,转到Java或者.NET,他们的内存管理基本是自动的,当然你也放弃了自由和对内存的支配权,还放弃了C++超绝的性能。

内存分配方式

  在C++中,内存分为内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

(1)堆

  就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

(2)栈

  在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

(3)自由存储区

  就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。

(4)全局/静态存储区

  全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

(5)常量存储区

  这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。

1. 堆和栈的区别

(1)管理方式

  对于栈来讲,是由编译器自动管理,无需我们手工控制;

  对于堆来说,释放工作由程序员控制,容易产生memory leak;

(2)空间大小

  一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的;

  但是对于栈来讲,一般都是有一定的空间大小的,我们可以修改;

(3)生长方向

  对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;

  对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长;

(4)分配方式

  堆都是动态分配的,没有静态分配的堆;

  栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。

  动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。

(5)分配效率

  栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高;

  堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低

(6)能否产生碎片

  对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低;

  对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列;

控制C++内存分配

  在嵌入式系统中使用C++的一个常见问题是内存分配,即对new 和 delete 操作符的失控。

  具有讽刺意味的是,问题的根源却是C++对内存的管理非常的容易而且安全。具体地说,当一个对象被消除时,它的析构函数能够安全的释放所分配的内存。

  这当然是个好事情,但是这种使用的简单性使得程序员们过度使用new 和 delete,而不注意在嵌入式C++环境中的因果关系。并且,在嵌入式系统中,由于内存的限制,频繁的动态分配不定大小的内存会引起很大的问题以及堆破碎的风险。

  作为忠告,保守的使用内存分配是嵌入式环境中的第一原则。

  但当你必须要使用new 和delete时,你不得不控制C++中的内存分配。你需要用一个全局的new 和delete来代替系统的内存分配符,并且一个类一个类的重载new 和delete。

  一个防止堆破碎的通用方法是从不同固定大小的内存持中分配不同类型的对象。对每个类重载new 和delete就提供了这样的控制。

1. 为单个类重载new和delete操作符

class TestClass {
public:
TestClass()
{
cout << "TestClass()" << endl;
}
~TestClass()
{
cout << "~TestClass()" << endl;
}
void * operator new(size_t size);
void operator delete(void *p);
}; void *TestClass::operator new(size_t size)
{
cout << "operator new" << endl;
void *p = malloc(size);
return (p);
} void TestClass::operator delete(void *p)
{
cout << "operator delete" << endl;
free(p);
} void main()
{
TestClass *pTest = new TestClass;
delete pTest;
pTest = NULL;
}

2. 为单个类重载new[]和delete[]操作符

class TestClass {
public:
TestClass()
{
cout << "TestClass()" << endl;
}
~TestClass()
{
cout << "~TestClass()" << endl;
}
void * operator new[](size_t size);
void operator delete[](void *p);
}; void *TestClass::operator new[](size_t size)
{
cout << "operator new" << endl;
void *p = malloc(size);
return (p);
} void TestClass::operator delete[](void *p)
{
cout << "operator delete" << endl;
free(p);
} void main()
{
TestClass *pTest = new TestClass[];
delete[] pTest;
pTest = NULL;
return;
}

特别特别要注意size_t nSize的大小!
C++new和delete实现原理:https://blog.csdn.net/passion_wu128/article/details/38966581

常见的内存错误及其对策

1. 常见内存错误

  (1) 内存分配未成功,却使用了它

  (2)内存分配虽然成功,但是尚未初始化就引用它

  (3)内存分配成功并且已经初始化,但操作越过了内存的边界

  (4)忘记了释放内存,造成内存泄露

  (5) 释放了内存却继续使用它

2. 对策

  (1)用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存

  (2)不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用

  (3)避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作

  (4)动态内存的申请与释放必须配对,防止内存泄漏

  (5)用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”

C++之内存管理的更多相关文章

  1. .NET基础拾遗(1)类型语法基础和内存管理基础

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开发基 ...

  2. PHP扩展-生命周期和内存管理

    1. PHP源码结构 PHP的内核子系统有两个,ZE(Zend Engine)和PHP Core.ZE负责将PHP脚本解析成机器码(也成为token符)后,在进程空间执行这些机器码:ZE还负责内存管理 ...

  3. linux2.6 内存管理——逻辑地址转换为线性地址(逻辑地址、线性地址、物理地址、虚拟地址)

    Linux系统中的物理存储空间和虚拟存储空间的地址范围分别都是从0x00000000到0xFFFFFFFF,共4GB,但物理存储空间与虚拟存储空间布局完全不同.Linux运行在虚拟存储空间,并负责把系 ...

  4. linux2.6 内存管理——概述

    在紧接着相当长的篇幅中,都是围绕着Linux如何管理内存进行阐述,在内核中分配内存并不是一件非常容易的事情,因为在此过程中必须遵从内核特定的状态约束.linux内存管理建立在基本的分页机制基础上,在l ...

  5. Objective-C内存管理之引用计数

    初学者在学习Objective-c的时候,很容易在内存管理这一部分陷入混乱状态,很大一部分原因是没有弄清楚引用计数的原理,搞不明白对象的引用数量,这样就当然无法彻底释放对象的内存了,苹果官方文档在内存 ...

  6. Quartz2D内存管理

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px "PingFang SC"; color: #239619 } p.p2 ...

  7. 浅谈Linux内存管理机制

    经常遇到一些刚接触Linux的新手会问内存占用怎么那么多?在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在这 ...

  8. linux内存管理

    一.Linux 进程在内存中的数据结构 一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分:    1) 代码段:存放CPU执行的机器指令.通常代码区是共享的,即其它执行程 ...

  9. cocos2d-x内存管理

    Cocos2d-x内存管理 老师让我给班上同学讲讲cocos2d-x的内存管理,时间也不多,于是看了看源码,写了个提纲和大概思想 一.   为什么需要内存管理 1. new和delete 2. 堆上申 ...

  10. Swift中的可选链与内存管理(干货系列)

    干货之前:补充一下可选链(optional chain) class A { var p: B? } class B { var p: C? } class C { func cm() -> S ...

随机推荐

  1. SqlHelper简单实现(通过Expression和反射)4.对象反射Helper类

    ObjectHelper的主要功能有: 1.通过反射获取Entity的实例的字段值和表名,跳过自增键并填入Dictionary<string,string>中. namespace RA. ...

  2. html 基础 超链接

    ***设置超链接的样式示例  a:link 超链接被点前状态 a:visited 超链接点击后状态 a:hover 悬停在超链接时 a:active 点击超链接时 在定义这些状态时,有一个顺序l v ...

  3. redis 笔记05 Sentinel、集群

    Sentinel 1. Sentinel只是一个运行在特殊模式下的Redis服务器,它使用了和普通模式不同的命令表,所以Sentinel模式能够使用的命令和普通的Redis服务器能够使用的命令不同. ...

  4. npm国内镜像设置

    http://cnodejs.org/topic/4f9904f9407edba21468f31e

  5. 【hihocoder】三十九周:二分.归并排序之逆序对

    就是用归并排序求数组中得逆序对.假设数组为a:[2 4 5],和b:[1 3],那么在这一次归并的时候逆序对这样求,belement表示当前result数组中b数组对应的元素个数,total表示逆序对 ...

  6. 【Flask】Flask Session操作

    ### session:1. session的基本概念:session和cookie的作用有点类似,都是为了存储用户相关的信息.不同的是,cookie是存储在本地浏览器,session是一个思路.一个 ...

  7. 基于SSM的单点登陆04

    jdbc.properties JDBC_DRIVER=org.mariadb.jdbc.Driver JDBC_URL=jdbc:mariadb://127.0.0.1:3306/market JD ...

  8. shell脚本多进程

    shell脚本再执行过程中就一个进程,从头到尾 下面配置shell脚本执行过程中启动多个进程同时执行 #!/bin/bash for ((i=1;i<=10;i++)) do ( echo &q ...

  9. ubuntu 致命错误: zlib.h:没有那个文件或目录【转】

    本文转载自:https://blog.csdn.net/u013359794/article/details/44922685?locationnum=15&fps=1 编译时,出现错误,提示 ...

  10. [CF489D]Unbearable Controversy of Being

    题目大意:求有向图中这种图的数量 从分层图来考虑,这是一个层数为3的图 枚举第一个点能到达的所有点,对他们进行BFS求第三层的点(假装它是BFS其实直接枚举效果一样) 代码: #include< ...