我目前正在开发一个嵌入式项目(STM32F103RB,CooCox CoIDE v.1.7.6 with arm-none-eabi-gcc 4.8 2013q4),我试图理解malloc()
在普通C
上的行为{ RAM已满时{1}}。
我的STM32有20kB = 0x5000Bytes的RAM,0x200用于堆栈。
#include <stdlib.h>
#include "stm32f10x.h"
struct list_el {
char weight[1024];
};
typedef struct list_el item;
int main(void)
{
item * curr;
// allocate until RAM is full
do {
curr = (item *)malloc(sizeof(item));
} while (curr != NULL);
// I know, free() is missing. Program is supposed to crash
return 0;
}
一旦堆太小而无法分配,我希望malloc()
返回NULL
:
0x5000
(RAM) - 0x83C
(bss) - 0x200
(堆叠)= 0x45C4
(堆)
所以当第18次执行malloc()
时。一项是1024 = 0x400
字节大。
但相反,uC在第18次之后调用HardFault_Handler(void)
(甚至不是MemManager_Handler(void)
)
是否有人建议如何预测malloc()
失败 - 因为等待NULL
返回似乎不起作用。
谢谢。
答案 0 :(得分:21)
malloc
看起来根本不做任何检查。您遇到的错误来自硬件检测到对无效地址的写入,该地址可能来自malloc
本身。
当malloc
分配内存时,它从内部池中获取一个块,并将其返回给您。但是,它需要存储free
函数的一些信息才能完成释放。通常,这是块的实际长度。为了保存这些信息,malloc
从块本身的开头起占用几个字节,在那里写入信息,然后将地址返回到已写入自己信息的地点。
例如,让我们说你要求一个10字节的块。 malloc
将获取一个可用的16字节块,比如地址0x3200..0x320F
,将长度(即16)写入字节1和2,然后将0x3202
返回给您。现在,您的程序可以使用从0x3202
到0x320B
的十个字节。其他四个字节也可用 - 如果您调用realloc
并要求14个字节,则不会重新分配。
当malloc
将长度写入要返回给你的内存块时,关键点就出现了:它写入的地址需要有效。似乎在第18次迭代之后,下一个块的地址是负的(转换为非常大的正值),因此CPU捕获写入,并触发硬故障。
在堆和堆栈相互增长的情况下,没有可靠的方法来检测内存不足,同时让你使用内存的每个最后一个字节,这通常是非常理想的事情。 malloc
无法预测分配后您将使用多少堆栈,因此它甚至都没有尝试。这就是为什么在大多数情况下字节计数在你身上。
一般情况下,在空间限制为几十千字节的情况下,在嵌入式硬件上,您可以避免{&#34}任意&#34;地方。相反,您使用一些预先计算的限制预先分配所有内存,并将其分配给需要它的结构,而不再重新调用malloc
。
答案 1 :(得分:4)
您的程序很可能因非法内存访问而崩溃,这几乎总是合法内存访问的间接(后续)结果,但您执行了一次不打算表演。
例如(这也是我对您系统上发生的事情的猜测):
你的堆最有可能在堆栈之后开始。现在,假设您在main
中有一个堆栈溢出。然后,您在main
中执行的其中一个操作(就您而言自然是合法操作)会使用一些“垃圾”数据覆盖堆的开头。
作为后续结果,下次尝试从堆中分配内存时,指向下一个可用内存块的指针不再有效,最终导致内存访问冲突。
首先,我强烈建议您将堆栈大小从0x200字节增加到0x400字节。这通常在链接器命令文件中定义,或通过IDE在项目的链接器设置中定义。
如果您的项目是IAR,那么您可以在icf
文件中更改它:
define symbol __ICFEDIT_size_cstack__ = 0x400
除此之外,我建议您在HardFault_Handler
中添加代码,以便在崩溃之前重建调用堆栈并注册值。这可能允许您跟踪运行时错误并找出确切的位置。
在文件'startup_stm32f03xx.s'中,确保您拥有以下代码:
EXTERN HardFault_Handler_C ; this declaration is probably missing
__tx_vectors ; this declaration is probably there
DCD HardFault_Handler
然后,在同一个文件中,添加以下中断处理程序(所有其他处理程序所在的位置):
PUBWEAK HardFault_Handler
SECTION .text:CODE:REORDER(1)
HardFault_Handler
TST LR, #4
ITE EQ
MRSEQ R0, MSP
MRSNE R0, PSP
B HardFault_Handler_C
然后,在文件'stm32f03xx.c'中,添加以下ISR:
void HardFault_Handler_C(unsigned int* hardfault_args)
{
printf("R0 = 0x%.8X\r\n",hardfault_args[0]);
printf("R1 = 0x%.8X\r\n",hardfault_args[1]);
printf("R2 = 0x%.8X\r\n",hardfault_args[2]);
printf("R3 = 0x%.8X\r\n",hardfault_args[3]);
printf("R12 = 0x%.8X\r\n",hardfault_args[4]);
printf("LR = 0x%.8X\r\n",hardfault_args[5]);
printf("PC = 0x%.8X\r\n",hardfault_args[6]);
printf("PSR = 0x%.8X\r\n",hardfault_args[7]);
printf("BFAR = 0x%.8X\r\n",*(unsigned int*)0xE000ED38);
printf("CFSR = 0x%.8X\r\n",*(unsigned int*)0xE000ED28);
printf("HFSR = 0x%.8X\r\n",*(unsigned int*)0xE000ED2C);
printf("DFSR = 0x%.8X\r\n",*(unsigned int*)0xE000ED30);
printf("AFSR = 0x%.8X\r\n",*(unsigned int*)0xE000ED3C);
printf("SHCSR = 0x%.8X\r\n",SCB->SHCSR);
while (1);
}
如果在执行此特定硬故障中断时无法在执行时使用printf
,则将所有上述数据保存在全局缓冲区中,以便在到达{后可以查看它{1}}。
然后,请参阅http://www.keil.com/appnotes/files/apnt209.pdf上的'Cortex-M故障异常和寄存器'部分以了解问题,或者如果您需要进一步的帮助,请在此处发布输出。
<强>更新强>
除上述所有内容外,请确保正确定义堆的基址。它可能在项目设置中进行硬编码(通常在数据部分和堆栈之后)。但它也可以在运行时,程序的初始化阶段确定。通常,您需要检查数据部分的基地址和程序堆栈(在构建项目后创建的映射文件中),并确保堆中的任何一个都不重叠。
我曾经遇到过将堆的基地址设置为常量地址的情况,这一点很好。但随后我通过向程序添加全局变量逐渐增加了数据部分的大小。堆栈位于数据部分的正后方,随着数据部分变大,它“向前移动”,因此它们中的任何一个都没有问题。但最终,堆被“分配”在堆栈的“顶部”。所以在某些时候,堆操作开始覆盖堆栈上的变量,堆栈操作开始覆盖堆的内容。
答案 2 :(得分:3)
使用标准c malloc
很难区分,malloc
似乎从我看来是错误的。因此,您可以使用RAM地址实现一些自定义malloc
来管理内存。
我不确定这可能对您有所帮助,但我在控制器相关项目中做了一些自定义malloc
,如下所示
#define LENGTH_36_NUM (44)
#define LENGTH_52_NUM (26)
#define LENGTH_64_NUM (4)
#define LENGTH_128_NUM (5)
#define LENGTH_132_NUM (8)
#define LENGTH_256_NUM (8)
#define LENGTH_512_NUM (18)
#define LENGTH_640_NUM (8)
#define LENGTH_1536_NUM (6)
#define CUS_MEM_USED (1)
#define CUS_MEM_NO_USED (0)
#define CALC_CNT (0)
#define CALC_MAX (1)
#define __Ram_Loc__ (0x20000000) ///This is my RAM address
#define __TOP_Ram_Loc__ (0x20000000 + 0x8000 -0x10) //Total 32K RAM and last 16 bytes reserved for some data storage
typedef struct _CUS_MEM_BLOCK_S {
char used;
int block_size;
char *ptr;
char *next;
} cus_mem_block_s;
static struct _MEM_INFO_TBL_S {
int block_size;
int num_max;
cus_mem_block_s *wm_head;
int calc[2];
} memInfoTbl[] = {
{36, LENGTH_36_NUM , 0, {0,0} },
{52, LENGTH_52_NUM , 0, {0,0} },
{64, LENGTH_64_NUM , 0, {0,0} },
{128, LENGTH_128_NUM , 0, {0,0} },
{132, LENGTH_132_NUM , 0, {0,0} },
{256, LENGTH_256_NUM , 0, {0,0} },
{512, LENGTH_512_NUM , 0, {0,0} },
{640, LENGTH_640_NUM , 0, {0,0} },
{1536,LENGTH_1536_NUM, 0, {0,0} },
};
#define MEM_TBL_MAX (sizeof(memInfoTbl)/sizeof(struct _MEM_INFO_TBL_S))
BOOL MemHeapHasBeenInitialised = FALSE;
这个基本的宏定义了RAM地址,并且手动为块大小手动选择了更多的块号,这经常需要分配,像36个字节需要我更多,所以我需要更多的数字。
这是mem init的初始化函数
void cus_MemInit(void)
{
int i,j;
cus_mem_block_s *head=NULL;
unsigned int addr;
addr = __Ram_Loc__;
for(i=0; i<MEM_TBL_MAX; i++)
{
head = (char *)addr;
memInfoTbl[i].wm_head = head;
for(j=0;j<memInfoTbl[i].num_max; j++)
{
head->used =CUS_MEM_NO_USED;
head->block_size = memInfoTbl[i].block_size;
head->ptr = (char *)(addr + sizeof(cus_mem_block_s));
addr += (memInfoTbl[i].block_size + sizeof(cus_mem_block_s));
head->next =(char *)addr;
head = head->next;
if(head > __TOP_Ram_Loc__)
{
printf("%s:error.\n",__FUNCTION__);
return;
}
}
}
head->ptr = 0;
head->block_size = 0;
head->next = __Ram_Loc__;
MemHeapHasBeenInitialised=TRUE;
}
这个用于分配
void* CUS_Malloc( int wantedSize )
{
void *pwtReturn = NULL;
int i;
cus_mem_block_s *head;
if(MemHeapHasBeenInitialised == FALSE)
goto done_exit;
for(i=0; i<MEM_TBL_MAX; i++)
{
if(wantedSize <= memInfoTbl[i].block_size)
{
head = memInfoTbl[i].wm_head;
while(head->ptr)
{
if(head->used == CUS_MEM_NO_USED)
{
head->used = CUS_MEM_USED;
pwtReturn = head->ptr;
goto done;
}
head = head->next;
}
goto done;
}
}
done:
if(pwtReturn)
{
for(i=0; i<MEM_TBL_MAX; i++)
{
if(memInfoTbl[i].block_size == head->block_size)
{
memInfoTbl[i].calc[CALC_CNT]++;
if(memInfoTbl[i].calc[CALC_CNT] > memInfoTbl[i].calc[CALC_MAX] )
memInfoTbl[i].calc[CALC_MAX]=memInfoTbl[i].calc[CALC_CNT];
break;
}
}
}
done_exit:
return pwtReturn;
}
这是免费的
void CUS_Free(void *pm)
{
cus_mem_block_s *head;
char fault=0;
if( (pm == NULL) || (MemHeapHasBeenInitialised == FALSE) )
goto done;
if( (pm < __RamAHB32__) && (pm > __TOP_Ram_Loc__) )
{
printf("%s:over memory range\n",__FUNCTION__);
goto done;
}
head = pm-sizeof(cus_mem_block_s);
if(head->used)
head->used = CUS_MEM_NO_USED;
else
{
printf("%s:free error\n",__FUNCTION__);
fault=1;
}
if(fault)
goto done;
int i;
for(i=0;i<MEM_TBL_MAX;i++)
{
if(memInfoTbl[i].block_size == head->block_size)
{
memInfoTbl[i].calc[CALC_CNT]--;
goto done;
}
}
done:;
}
毕竟你可以使用上面的功能,如
void *mem=NULL;
mem=CUS_Malloc(wantedsize);
然后还可以按如下方式观看您使用的内存
void CUS_MemShow(void)
{
int i;
int block_size;
int block_cnt[MEM_TBL_MAX];
int usedSize=0, totalSize=0;
cus_mem_block_s *head;
if(MemHeapHasBeenInitialised == FALSE)
return;
memset(block_cnt, 0, sizeof(block_cnt));
head = memInfoTbl[0].wm_head;
i=0;
block_size = head->block_size;
vTaskSuspendAll();
while( head->ptr !=0)
{
if(head->used == CUS_MEM_USED )
{
block_cnt[i]++;
usedSize +=head->block_size;
}
usedSize += sizeof(cus_mem_block_s);
totalSize += (head->block_size+ sizeof(cus_mem_block_s));
/* change next memory block */
head = head->next;
if( block_size != head->block_size)
{
block_size = head->block_size;
i++;
}
}
xTaskResumeAll();
usedSize += sizeof(cus_mem_block_s);
totalSize+= sizeof(cus_mem_block_s);
dprintf("----Memory Information----\n");
for(i=0; i<MEM_TBL_MAX; i++) {
printf("block %d used=%d/%d (max %d)\n",
memInfoTbl[i].block_size, block_cnt[i],
memInfoTbl[i].num_max,
memInfoTbl[i].calc[CALC_MAX]);
}
printf("used memory=%d\n",usedSize);
printf("free memory=%d\n",totalSize-usedSize);
printf("total memory=%d\n",totalSize);
printf("--------------------------\n");
}
一般情况下先预先计算内存然后按照我的要求给出。
答案 3 :(得分:3)
arm-none-eabi-*
工具链发行版包括 newlib C库。为嵌入式系统配置newlib时,用户程序必须 provide an _sbrk()
function 才能正常工作。
malloc()
仅依靠_sbrk()
来确定堆内存的开始位置和结束位置。对_sbrk()
的第一次调用返回堆的开始,如果所需的内存量不可用,随后的调用应返回-1
,然后malloc()
依次将NULL
返回到应用程序。您的_sbrk()
看起来很糟,因为它显然使您分配的内存超出了可用内存。您应该能够对其进行修复,以使其在预期堆与堆栈发生冲突之前返回-1
。
答案 4 :(得分:0)
在这里,您可以找到我如何“强制” malloc()返回NULL(如果堆太小而无法根据berendi的先前答案进行分配)。我估计了堆栈的最大数量,并据此可以计算出在最坏情况下堆栈可以启动的地址。
#define STACK_END_ADDRESS 0x20020000
#define STACK_MAX_SIZE 0x0400
#define STACK_START_ADDRESS (STACK_END_ADDRESS - STACK_MAX_SIZE)
void * _sbrk_r(
struct _reent *_s_r,
ptrdiff_t nbytes)
{
char *base; /* errno should be set to ENOMEM on error */
if (!heap_ptr) { /* Initialize if first time through. */
heap_ptr = end;
}
base = heap_ptr; /* Point to end of heap. */
#ifndef STACK_START_ADDRESS
heap_ptr += nbytes; /* Increase heap. */
return base; /* Return pointer to start of new heap area. */
#else
/* End of heap mustn't exceed beginning of stack! */
if (heap_ptr <= (char *) (STACK_START_ADDRESS - nbytes) ) {
heap_ptr += nbytes; /* Increase heap. */
return base; /* Return pointer to start of new heap area. */
} else {
return (void *) -1; /* Return -1 means that memory run out */
}
#endif // STACK_START_ADDRESS
}