闲话
这几天自己在写一个小项目,在裸机下用多个定时器跑多任务(是的很扭曲。。。)
使用了MODBUS协议,所以在串口发送的时候为了能够节省一点sram,使用了malloc来进行动态分配发送数据的空间。
本来上星期bug都de完了,也验证过没什么问题了,周一来上班直接卡死 让我两眼一黑
正题
首先丢出一段十分简单的代码:
while (1)
{
static uint16_t *a;
a=malloc(3);
a[0]=0;
a[1]=0;
a[2]=0;
free(a);
uint8_t *b;
b=malloc(8);
b[0]=0x99;
free(b);
}
乍看之下好像没有什么问题,编译器也没有报错,一切正常。
然后跑起来就死机,进调试步进看发现在
b[0]=0x99;
这一句进入了HardFault中断,再细看发现是malloc(8)
根本没有返回一个有效指针,而是返回了空指针。
百度一下“单片机malloc分配失败的原因”
一般有以下两点:
- 内存不足。
- 在前面的程序中出现了内存的越界访问,导致malloc()分配函数所涉及的一些信息被破坏。下次再使用malloc()函数申请内存就会失败,返回空指针NULL(0)。
在调试界面我看了又看,试了又试,才恍然大悟——malloc默认分配char大小(也就是uint8_t)的空间,
并且!!! a=(uint16_t *)malloc(3);
同样是没有作用的。
(uint16_t *)
只能够帮助你将malloc返回的void *
类型指针强制转换为uint16_t *
类型。
那么答案显而易见——我的这段代码存在上述的第二个问题。
但是 你先别急
诶!帅哥美女,先别急着走啊!下面还有呐!
百度只告诉你:越界访问会导致malloc()
的某些重要信息被破坏,所以你申请内存就会失败。
各位,如果将上面的代码中稍微改动以下:
while (1)
{
static uint16_t *a;
a=malloc(3);
a[0]=0;
a[1]=0;
a[2]=0xffff; //将赋值为0改成0xffff
free(a);
uint8_t *b;
b=malloc(8);
b[0]=0x99;
free(b);
}
并进入调试,你将惊奇地发现:尽管已经越界赋值,篡改了某些看起来好像对malloc
来说很重要的数据,但好像程序也可以跑耶!还不会死机!好耶搞定了!本文完
“0”和“1”,为什么赋于不同的值,虽然都越界篡改了数据,但为什么结果却是大相径庭。
以下是来自一个什么都不懂的小白(我)的猜测:
实现像malloc()
一样的动态分配,最基础的东西需要有两个:
- 分配的内存地址
- 分配的内存大小
之后只需要返回这个内存地址给用户,并根据内存大小对内存空间进行管理,就可以实现最基本的动态分配。
而malloc()
的实现虽然没有这么简单,但大差不差——在此原理上加入各种优化。
假设我连续申请3个内存空间
static uint16_t *a,*d,*c;
a=malloc(3);
d=malloc(4);
c=malloc(5);
在内存中是长这样的:
红色框出的是我代码申请的内存空间。
而为什么实际申请的空间会多出这么多则是因为内存分配的一个准则:对齐。
在内存中可以看出32位单片机的malloc()
是32位对齐(听起来像是废话)
具体为什么要对齐可以参考:
而蓝色框中的数据可以简单地理解为malloc()
所产生的重要数据,在一开始的代码中a[2]=0;
正好会破坏第一个蓝色框中的08位置的数据,相当于破坏了分配出的内存空间的完整性,那么不管是malloc()
orfree()
都不会认它这个“孩子”。
可为什么第二段代码仅仅只是替换了赋于的值就“可以”了?
我的理解是:前两个蓝色框中的数据相当于“连接符”一样的东东,而第三个蓝色框才是关键:他代表着malloc()
在内存中分配的“尾巴”,一旦失去了这个“尾巴”,那么所分配的内存空间在整个内存当中相当于无限长,导致了所有内存都被占用,而free()
则只会释放malloc()
分配的大小的内存,结局就是只释放了正确分配的那一小部分空间,而剩下的大部分的内存空间都“被占用”了。
如果这个“尾巴”被赋于0值,则相当于直接消失,但若是赋于一个非0值,尽管数据已经被篡改,但“尾巴”仍然存在,在释放此内存空间的时候这个“尾巴”将成为内存碎片无法被消去,而“尾巴”后面的空间则不受影响。
(我猜测是malloc()
实现的一个安全机制,防止内存数据因意外导致变动后直接导致整个程序的崩溃)
源代码
while (1)
{
static uint16_t *a;
a=malloc(3);
a[0]=0;
a[1]=0;
a[2]=0;
free(a);
uint8_t *b;
b=malloc(8);
b[0]=0x99;
free(b);
}
以下是验证代码:
while (1)
{
static uint16_t *a;
a=(uint16_t *)malloc(11);
a[0]=0;
a[1]=0;
a[2]=0;
a[3]=0;
a[4]=0;
a[5]=0;
a[6]=0; //内存越界
free(a);
uint8_t *b;
b=malloc(16); //malloc返回一个空指针
b[0]=0x99; //调试执行这一句后进入HardFault
free(b);
}
使用malloc()
分配11个uint8_t *
大小的内存空间(实际将会分配到12字节的空间),那么在a[6]
时将会越过实际分配的内存地址
*图为a[6]=0;
未执行的时候,红框内为实际分配到的内存空间
解决方法
- 简单粗暴:在
free(a)
之前将所申请的“越界数组”的最后一个元素置1,即a[2]=1
(不推荐) - 使用
void *calloc(size_t /*nmemb*/, size_t /*size*/); //nmemb为数组长度,size为数据数据大小(单位为 字节)