内存划分:
一、静态区
1、内存越界:
数据区内存越界主要指读写某一数据区内存(如全局或静态变量、数组或结构体等)时,超出该内存区域的合法范围
读越界和写越界
读越界表示读取不属于自己的数据,如读取的字节数多于分配给目标变量的字节数。若所读的内存地址无效,则程序立即崩溃;若所读的内存地址有效,则可读到随机的数据,导致不可预料的后果。
写越界亦称“缓冲区溢出”,所写入的数据对目标地址而言也是随机的,因此同样导致不可预料的后果。
写越界的主要原因有两种:1) memset/memcpy/memmove等内存覆写调用;2) 数组下标超出范围。
解决方法:
头尾校验
修改只读数据区内容会引发段错误
关键字volatile用于修饰易变的变量,告诉编译器该变量值可能会在任意时刻被意外地改变,因此不要试图对其进行任何优化。每次访问(读写)volatile所修饰的变量时,都必须从该变量的内存区域中重新读取,而不要使用寄存器(CPU)中保存的值。这样可保证数据的一致性,防止由于变量优化而出错。
以下几种情况通常需要volatile关键字:
外围并行设备的硬件寄存器(如状态寄存器);
中断服务程序(ISR)中所访问的非自动变量(Non-automatic Variable),即全局变量;
多线程并发环境中被多个线程所共享的全局变量
二、堆区
1、内存没初始化
通过malloc库函数分配的动态内存,其初值未定义。若访问未初始化或未赋初值的内存,则会获得垃圾值。当基于这些垃圾值控制程序逻辑时,会产生不可预测的行为。
【对策】在malloc之后调用 memset 将内存初值清零
2、内存分配失败
动态内存成功分配的前提是系统具有足够大且连续可用的内存。内存分配失败的主要原因有:
1) 剩余内存空间不足;
2) 剩余内存空间充足,但内存碎片太多,导致申请大块内存时失败;
3) 内存越界,导致malloc等分配函数所维护的管理信息被破坏。
3、内存释放失败
内存释放失败的主要原因有:
1) 释放未指向动态内存的指针;
2) 指向动态内存的指针在释放前被修改;
3) 内存越界,导致malloc等分配函数所维护的管理信息被破坏;
4) 内存重复释放(Double Free)。
4、内存分配与释放不匹配
编码者一般能保证malloc和free配对使用,但可能调用不同的实现
【对策】动态内存的申请与释放接口调用方式和次数必须配对,防止内存泄漏。分配和释放最好由同一方管理,并提供专门的内存管理接口。若不能坚持谁申请谁释放,则应进行协商或加代码注释说明。
5、内存越界
【对策】当模块提供动态内存管理的封装接口时,可采用“红区”技术检测内存越界。例如,接口内每次申请比调用者所需更大的内存,将其首尾若干字节设置为特殊值,仅将中间部分的内存返回给调用者使用。这样,通过检查特殊字节是否被改写,即可获知是否发生内存越界。
6、内存泄漏
内存泄漏指由于疏忽或错误造成程序未能释放已不再使用的内存
内存泄漏的真正危害在于其累积性,这将最终耗尽系统所有的内存。因此,一次性内存泄漏并无大碍,因为它不会累积;而隐式内存泄漏危害巨大,因其相比常发性和偶发性内存泄漏更难检测。
内存泄漏的主要原因有:
1) 指向已申请内存的指针被挪作他用并被改写;
2) 因函数内分支语句提前退出,导致释放内存的操作未被执行;
3) 数据结构或处理流程复杂,导致某些应该释放内存的地方被遗忘;
4) 试图通过函数指针参数申请并传递动态内存;
5) 线程A分配内存,线程B操作并释放内存,但分配速度远大于释放速度。
【对策】设计时应规范各动态内存的用途及申请释放的流程,避免指针多用和忘记释放。
函数内部若存在退出分支,则每个返回之前都应确保释放已成功分配的内存。
对于挂接内存,应按照分配顺序反向遍历释放各子内存,最后释放父内存(最好能为其提供专门的分配和释放接口)。也可借助柔性数组特性来简化释放操作,尤其是当挂接内存提供给外部调用者使用时:
7、使用已释放堆内存
动态内存被释放后,其中的数据可能被应用程序或堆分配管理器修改。不要再试图访问这块已被释放的内存,否则可能导致不可预料的后果。
【对策】务必保证已分配的内存块被且仅被释放一次,禁止访问指向已释放内存的指针。若该指针还存在多个副本,则必须保证当它所指向的动态内存被释放后,不再使用所有其他副本。
避免上述错误发生的常用方法是释放内存后立即将对应的指针设置为空(NULL)。
三、栈区
1、内存未初始化
未初始化的栈区变量其内容为随机值。直接使用这些变量会导致不可预料的后果,且难以排查。
指针未初始化(野指针)或未有效初始化(如空指针)时非常危险,尤以野指针为甚。
【对策】在定义变量时就对其进行初始化。某些编译器会对未初始化发出警告信息,便于定位和修改。
2、堆栈溢出
每个线程堆栈空间有限,稍不注意就会引起堆栈溢出错误。注意,此处“堆栈”实指栈区。
堆栈溢出主要有两大原因:1) 过大的自动变量;2) 递归或嵌套调用层数过深。
3、内存越界
因其作用域和生存期限制,发生在栈区的内存越界相比数据区更易发现和排查
4、返回栈内存地址
(被调)函数内的局部变量在函数返回时被释放,不应被外部引用
【对策】不要用return语句返回指向栈内变量的指针,可改为返回指向静态变量或动态内存的指针。但两者都存在重入性问题,而且后者还存在内存泄露的危险
参考链接:
https://www.cnblogs.com/clover-toeic/p/3816644.html