我的编程空间,编程开发者的网络收藏夹
学习永远不晚

动态内存管理及防御性编程

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

动态内存管理及防御性编程

  • 栈区(stack)有时也称为堆栈,重点在栈字,存放函数内部临时变量
  • 堆区(heap)也就是动态申请(malloc)、释放(free)的内存区域
  • 数据区(data)初始化的全局变量和静态变量, 占用可执行文件空间;rodata 固定不变const修饰的全局变量,不占内存空间
  • bss区未初始化的全局变量、静态变量(static关键字描述的),初始化为全0的全局变量,不占用可执行文件大小
  • 代码区(text)程序二进制文件。

最终下载的可执行文件包括代码(text)和数据(data)。内存的分配一般如下图:

其中堆和栈的地址分配方向相反,栈比较特殊,下面微信公众号【嵌入式系统】以栈空间异常使用为例:

#include <stdio.h>

int main(void)
{
int a=100;
int b[3]={0};
int c=200;

printf("ori> a[%p]=%d,c[%p]=%d\r\n",&a,a,&c,c);

printf(" > b[%p]\r\n",&b);
b[0]=0;
b[1]=1;
b[2]=2;
b[3]=3;//error ->a

printf("new> a[%p]=%d,c[%p]=%d\r\n",&a,a,&c,c);

return 0;
}

运行结果:

ori> a[0028FEBC]=100,c[0028FEAC]=200
> b[0028FEB0]
new> a[0028FEBC]=3,c[0028FEAC]=200

结合打印的变量地址,栈空间分配如下图,因为数组b的操作越界,导致了变量a的值被覆盖。

针对个人情况,一般情况下内存溢出都是使用数组越界,所以在异常值后或者前查看有没数组(全局变量可以查map文件),检查数组的操作是否正确。

除了堆区,其他几个区都是有编译器和系统运行时自动处理的,而堆区由开发者来操作的。这既是便利,也是隐患,一旦操作失误就是内存泄漏或溢出。

2.动态内存管理

在硬件资源固定的情况下,栈和堆的空间此消彼长,合理的定义堆的空间,为不同任务分配合适的栈空间也是至关重要的。以FreeRTOS内核代码为例,《FreeRTOS及其应用》分别解读其5种动态内存,也就是堆的分配方式,其他系统的原理差不多。参考Guide文档 https://www.freertos.org/Documentation/RTOS_book.html Guide

FreeRTOS 内核提供了 5 种内存管理算法,源文件在Source\portable\MemMang 下,使用时选择其中一个。

heap_1.c内存管理方案简单,它只能申请内存而不能进行内存释放。

一些低端嵌入式系统并不会经常动态申请与释放内存,在系统启动后申请,一直使用下去,永不释放,适合这种方式,也可近似理解为多个全局小数组合并的使用。

heap_2.c 方案支持申请和释放,但是它不能把相邻的两个小的内存块合成一个大的内存块, 随着不断的申请释放,空闲空间会分割为很多小片段,如下图:

持续申请、释放一定次数,就会出现剩余空间的和较大,但却申请不到内存的情况,如上图剩余空间是900,但无法申请600,因为没有连续的600空间。如果每次申请内存大小都是固定的,就不存在内存碎片问题,但实际不会这样,因此不推荐。

heap_3.c 方案只是封装了标准 C 库中的 malloc()和 free()函数,由编译器提供,需要通过编译器或者启动文件设置堆空间,封装是为了保证线程安全。

heap_4.c 方案是在heap_2.c 基础上,对内存碎片进行了改进。

如图E到F,用户释放后,把相邻的空闲的内存块合并成一个更大的块,这样可以减少内存碎片。

heap_5.c 方案在实现动态内存分配时与 heap4.c 方案一样,采用最佳匹配算法和合并算法,并且允许内存堆跨越多个非连续的内存区,也就是允许在不连续的内存堆中实现内存分配,比如做图形显示,可能芯片内部的 RAM 不足,额外扩展SDRAM,这种内存管理方案则比较合适。

一般选用heap_4.c。

3.动态内存防御性编程

内存只申请不释放,运行一段时间会因为内存不足而无法运行,即内存泄露;或者操作的内存区域超出了申请的空间,访问越界即内存溢出,导致各种随机异常。对于内存操作的不稳定因素,如何进行防御性编程,可以在调试阶段发现问题?

简单的说就是内存分配的时候,记录申请内存的函数名(或者扩展加上申请时间),申请内存大小的基础上额外增加空间,在其首尾加入特殊的标志位,释放该内存前对标志位进行校验;如果校验不通过,则将申请该内存的函数名打印出来,表示出现了内存溢出。也支持随时打印当前动态内存的使用情况,查看某些函数申请的内存释放一直未被释放,人工判断是否内存泄露。

下面是完整源码:

//pal_memory.h
#ifndef _PAL_MEMORY_H
#define _PAL_MEMORY_H

//配置是否开启内存记录功能
#define __MEMORY_DEBUG__

typedef unsigned char uint8_t;
typedef unsigned int uint32_t;

extern void *chengj_pal_memory_malloc(uint32_t size, const char *func);
extern void chengj_pal_memory_free(void **pv);
extern void chengj_pal_memory_record_print(void);

#define chengj_malloc(size) chengj_pal_memory_malloc(size, __FUNCTION__)
#define chengj_free(pv) chengj_pal_memory_free(&pv)

#endif

具体实现:


#include <stddef.h>
#include <string.h>
#include "pal_memory.h"

//适配平台内存管理接口
#define PAL_MALLOC malloc
#define PAL_FREE free

#if defined (__MEMORY_DEBUG__)

#define MEMORY_RECORD_COUNT_MAX 100

//len[4]+head[4]+...[data]...+tail[2]
#define MEMORY_EXTRA_SIZE 10

//magic
#define MEMORY_DATA_MAGIC_HEAD 0x43
#define MEMORY_DATA_MAGIC_TAIL 0x4A

typedef struct
{
const char *func_name;
void *pointer;
//可扩展保存 时间戳 等信息
} memory_record_struct;

//记录申请内存的函数
static memory_record_struct chengj_memory_record[MEMORY_RECORD_COUNT_MAX] = {0};

#endif


void chengj_pal_memory_record_print(void)
{
#if defined (__MEMORY_DEBUG__)
uint32_t i = 0;

for(; i < MEMORY_RECORD_COUNT_MAX; i++)
{
if(chengj_memory_record[i].pointer != NULL)
{
printf("[%d] %s()\r\n", i, chengj_memory_record[i].func_name);
}
}
#endif
}


void *chengj_pal_memory_malloc(uint32_t size, const char *func)
{
void *pv = NULL;
uint32_t i = 0;
#if defined (__MEMORY_DEBUG__)
uint8_t *pdata;
#endif

if(size == 0 || func == NULL)
{
return NULL;
}

#if defined (__MEMORY_DEBUG__)
size = size + MEMORY_EXTRA_SIZE;
#endif

pv = PAL_MALLOC(size);

if(pv == NULL)
{
return NULL;
}
memset(pv, 0, size);

#if defined (__MEMORY_DEBUG__)

pdata = (uint8_t *)pv;

pdata[0] = (size >> 24) & 0xFF;
pdata[1] = (size >> 16) & 0xFF;
pdata[2] = (size >> 8) & 0xFF;
pdata[3] = size & 0xFF;

pdata[4] = MEMORY_DATA_MAGIC_HEAD;
pdata[5] = MEMORY_DATA_MAGIC_HEAD;
pdata[6] = MEMORY_DATA_MAGIC_HEAD;
pdata[7] = MEMORY_DATA_MAGIC_HEAD;

pdata[size - 2] = MEMORY_DATA_MAGIC_TAIL;
pdata[size - 1] = MEMORY_DATA_MAGIC_TAIL;

for(; i < MEMORY_RECORD_COUNT_MAX; i++) //过多不记录
{
if(chengj_memory_record[i].pointer == NULL)
{
chengj_memory_record[i].func_name = func;
chengj_memory_record[i].pointer = pv;
break;
}
}

return &pdata[8];

#else

return pv;

#endif

}


void chengj_pal_memory_free(void **pv)
{
uint32_t i = 0;
#if defined (__MEMORY_DEBUG__)
uint32_t size;
uint8_t *pdata;
uint8_t error = 0;
#endif

if(pv == NULL || *pv == NULL)
{
return;
}

#if defined (__MEMORY_DEBUG__)

pdata = (uint8_t *)(*pv) - 8;
*pv = (void*)pdata;

size = ((pdata[0] << 24) | (pdata[1] << 16) | (pdata[2] << 8) | (pdata[3]));

if(size <= MEMORY_EXTRA_SIZE)
{
error = error | 0x01;
}

if((pdata[4] != MEMORY_DATA_MAGIC_HEAD) || (pdata[5] != MEMORY_DATA_MAGIC_HEAD)\
|| (pdata[6] != MEMORY_DATA_MAGIC_HEAD) || (pdata[7] != MEMORY_DATA_MAGIC_HEAD))
{
error = error | 0x02;
}

if((pdata[size - 2] != MEMORY_DATA_MAGIC_TAIL) || (pdata[size - 1] != MEMORY_DATA_MAGIC_TAIL))
{
error = error | 0x04;
}


for(; i < MEMORY_RECORD_COUNT_MAX; i++)
{
if(chengj_memory_record[i].pointer == *pv)
{
if(error != 0)
{
if(chengj_memory_record[i].func_name != NULL)
{
printf("memory error 0x%02X %s()\r\n", error, chengj_memory_record[i].func_name);
}
else
{
printf("memory error 0x%02X %p\r\n", error, *pv);
}
}
memset(&chengj_memory_record[i], 0, sizeof(memory_record_struct));
break;
}
}

if(error != 0)
{
//ASSERT调试
return;
}
#endif

PAL_FREE(*pv);
*pv = NULL;

return;
}

可以测试下效果:

#include "pal_memory.h"

//微信公众号: 嵌入式系统
//申请10字节但使用20字节
void test(void)
{
uint8_t *p;
uint8_t i;

p=chengj_malloc(10);
for(i=0;i<20;i++)
{
p[i]=i;
}
chengj_free(p);
}


int main(int argc, char *argv[])
{
printf("embedded-system \r\n");
test();
return 0;
}

运行结果:

embedded-system
memory error 0x04 test()

表示test函数内申请的一段内存使用时溢出,尾部标记数据被覆盖。

也可以在memory_record_struct增加时间戳成员,记录内存申请时间,再扩展void chengj_pal_memory_record_print(void) 打印内存使用情况,查看长时间申请未释放的内存使用情况。针对日志信息缩小内存异常的范围,关于软件bug调试与解决,可以参考《嵌入式软件bug从哪来,怎么去》。

4.小节

内存记录调试方法,浪费了一定量的内存空间,而且不能排除问题,只是提早监测到异常,但对软件稳定性仍有较大意义,可以快速解决内存问题。建议只在debug版本启用,正式发布的release版本关闭记录功能。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

动态内存管理及防御性编程

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

动态内存管理及防御性编程

内存记录调试方法,浪费了一定量的内存空间,而且不能排除问题,只是提早监测到异常,但对软件稳定性仍有较大意义,可以快速解决内存问题。建议只在debug版本启用,正式发布的release版本关闭记录功能。

详解C++中动态内存管理和泛型编程

这篇文章主要为大家详细介绍了C++中动态内存管理和泛型编程的相关资料,文中示例代码讲解详细,对我们学习C++具有一定帮助,感兴趣的小伙伴快跟随小编一起学习一
2022-11-13

C语言动态内存管理的原理及实现方法

C语言动态内存管理的原理是通过malloc()函数申请一块连续的内存空间,并返回其地址,通过free()函数释放该内存空间。实现方法是通过在程序运行时动态地管理内存,即在需要内存时申请,不需要时释放,避免了静态内存分配的浪费和不足
2023-05-16

编译器的自动内存管理,静态的GC算法

写过C语言的都知道,多线程的野指针是非常难查的BUG,因为程序跑飞了不知道会core在哪里,而且BUG也不是必现的。

C++new与malloc和delete及free动态内存管理及区别介绍

这篇文章主要介绍了深入理解C++中的new/delete和malloc/free动态内存管理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2022-12-19

C语言动态内存管理malloc柔性数组示例详解

这篇文章主要为大家介绍了C语言动态内存管理malloc柔性数组示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

C++ 元编程在元数据管理和动态属性访问中的作用?

c++++ 元编程在元数据管理和动态属性访问中发挥重要作用:元数据管理:使用模板和编译时计算管理类属性的元数据,可在运行时访问。动态属性访问:使用 decltype 实现动态属性访问,允许在运行时获取和设置对象的属性。C++ 元编程在元数据
C++ 元编程在元数据管理和动态属性访问中的作用?
2024-05-21

程序运行后性能总会下降?你应该先了解编程语言的内存布局与管理

当今流行的编程语言,大多具备垃圾回收(Garbage Collection,以下简称GC)功能。它能够将不再使用的内存区域收回并重新分配。

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录