C语言 嵌入式系统上的 malloc 行为
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22422733/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
malloc behaviour on an embedded system
提问by Boern
I'm currently working on an embedded project (STM32F103RB, CooCox CoIDE v.1.7.6 with arm-none-eabi-gcc 4.8 2013q4) and I'm trying to understand how malloc()behaves on plain Cwhen the RAM is full.
我目前正在研究一个嵌入式项目(STM32F103RB、CooCox CoIDE v.1.7.6 with arm-none-eabi-gcc 4.8 2013q4),我试图了解当 RAM 已满时malloc()在普通C情况下的行为。
My STM32 has 20kB = 0x5000Bytes of RAM, 0x200 are used for the stack.
我的 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;
}
I would expect malloc()to return NULLas soon as the heap is too small for allocating:
我希望在堆太小而无法分配时立即malloc()返回NULL:
0x5000(RAM) - 0x83C(bss) - 0x200(stack) = 0x45C4(heap)
0x5000(RAM) - 0x83C(bss) - 0x200(stack) = 0x45C4(heap)
So when executing the malloc()for the 18th time. One item is 1024=0x400Bytes large.
所以在malloc()第 18 次执行时。一项是 1024 =0x400字节大。
But instead the uC calls the HardFault_Handler(void)after the 18th time (not even the MemManager_Handler(void))
但是 uCHardFault_Handler(void)在第 18 次之后调用了(甚至不是MemManager_Handler(void))
Does anybody have an advice how to forecast a malloc()failure - since waiting for a NULLreturn doesn't seem to work.
有没有人有关于如何预测malloc()失败的建议- 因为等待NULL返回似乎不起作用。
Thank you.
谢谢你。
采纳答案by dasblinkenlight
It does not look like mallocis doing any checks at all. The fault that you get comes from hardware detecting a write to an invalid address, which is probably coming from mallocitself.
看起来根本没有malloc进行任何检查。你得到的错误来自硬件检测到一个无效地址的写入,这可能来自malloc它本身。
When mallocallocates memory, it takes a chunk from its internal pool, and returns it to you. However, it needs to store some information for the freefunction to be able to complete deallocation. Usually, that's the actual length of the chunk. In order to save that information, malloctakes a few bytes from the beginning of the chunk itself, writes the info there, and returns you the address past the spot where it has written its own information.
当malloc分配内存,它从它的内部池块,并返回给你。但是,它需要为free函数存储一些信息才能完成释放。通常,这是块的实际长度。为了保存该信息,malloc从块本身的开头取几个字节,将信息写入那里,然后返回超过它写入自己信息的位置的地址。
For example, let's say you asked for a 10-byte chunk. mallocwould grab an available 16-byte chunk, say, at addresses 0x3200..0x320F, write the length (i.e. 16) into bytes 1 and 2, and return 0x3202back to you. Now your program can use ten bytes from 0x3202to 0x320B. The other four bytes are available, too - if you call reallocand ask for 14 bytes, there would be no reallocation.
例如,假设您要求一个 10 字节的块。malloc将获取一个可用的 16 字节块,例如,在addresses 处0x3200..0x320F,将长度(即16)写入字节1 和2,然后返回0x3202给您。现在您的程序可以使用从0x3202到 的十个字节0x320B。其他四个字节也可用 - 如果您打电话realloc要求提供 14 个字节,则不会重新分配。
The crucial point comes when mallocwrites the length into the chunk of memory that it is about to return to you: the address to which it writes needs to be valid. It appears that after the 18-th iteration the address of the next chunk is negative (which translates to a very large positive) so CPU traps the write, and triggers the hard fault.
当malloc将长度写入即将返回给您的内存块时,关键点就出现了:它写入的地址必须是有效的。似乎在第 18 次迭代之后,下一个块的地址为负(转换为非常大的正),因此 CPU 捕获写入,并触发硬故障。
In situations when the heap and the stack grow toward each other there is no reliable way to detect an out of memory while letting you use every last byte of memory, which is often a very desirable thing. malloccannot predict how much stack you are going to use after the allocation, so it does not even try. That is why the byte counting in most cases is on you.
在堆和堆栈相互增长的情况下,没有可靠的方法来检测内存不足,同时让您使用内存的每个最后一个字节,这通常是非常可取的。malloc无法预测分配后您将使用多少堆栈,因此它甚至不尝试。这就是为什么在大多数情况下字节计数由您决定。
In general, on embedded hardware when the space is limited to a few dozen kilobytes, you avoid malloccalls in "arbitrary" places. Instead, you pre-allocate all your memory upfront using some pre-calculated limits, and parcel it out to structures that need it, and never call mallocagain.
通常,在空间限制为几十 KB 的嵌入式硬件上,您可以避免malloc在“任意”位置调用。相反,您使用一些预先计算的限制预先分配所有内存,并将其分配给需要它的结构,并且不再调用malloc。
回答by barak manos
Your program most likely crashes because of an illegal memory access, which is almost always an indirect (subsequent) result of a legal memory access, but one that you did not intend to perform.
你的程序很可能崩溃,因为一个非法的内存访问,这是几乎总是一个间接的(后续)结果合法内存访问,而是一个你不打算执行。
For example (which is also my guess as to what's happening on your system):
例如(这也是我对您系统上发生的事情的猜测):
Your heap most likely begins right after the stack. Now, suppose you have a stack-overflow in main. Then one of the operations that you perform in main, which is naturally a legal operation as far as you're concerned, overrides the beginning of the heap with some "junk" data.
您的堆很可能在堆栈之后立即开始。现在,假设您在main. 然后,您在 中执行的main操作之一(就您而言这自然是合法的操作)用一些“垃圾”数据覆盖堆的开头。
As a subsequent result, the next time that you attempt to allocate memory from the heap, the pointer to the next available chunk of memory is no longer valid, eventually leading to a memory access violation.
结果是,下次尝试从堆分配内存时,指向下一个可用内存块的指针不再有效,最终导致内存访问冲突。
So to begin with, I strongly recommend that you increase the stack size from 0x200 bytes to 0x400 bytes. This is typically defined within the linker-command file, or through the IDE, in the project's linker settings.
因此,首先,我强烈建议您将堆栈大小从 0x200 字节增加到 0x400 字节。这通常在链接器命令文件中定义,或通过 IDE 在项目的链接器设置中定义。
If your project is on IAR, then you can change it in the icffile:
如果您的项目在 IAR 上,那么您可以在icf文件中更改它:
define symbol __ICFEDIT_size_cstack__ = 0x400
Other than that, I suggest that you add code in your HardFault_Handler, in order to reconstruct the call-stack and register values prior to the crash. This might allow you to trace the runtime error and find out exactly where it happened.
除此之外,我建议你在你的 中添加代码HardFault_Handler,以便在崩溃之前重建调用堆栈和寄存器值。这可能允许您跟踪运行时错误并找出它发生的确切位置。
In file 'startup_stm32f03xx.s', make sure that you have the following piece of code:
在文件“startup_stm32f03xx.s”中,确保您有以下代码:
EXTERN HardFault_Handler_C ; this declaration is probably missing
__tx_vectors ; this declaration is probably there
DCD HardFault_Handler
Then, in the same file, add the following interrupt handler (where all other handlers are located):
然后,在同一个文件中,添加以下中断处理程序(所有其他处理程序所在的位置):
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
Then, in file 'stm32f03xx.c', add the following ISR:
然后,在文件“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);
}
If you can't use printfat the point in the execution when this specific Hard-Fault interrupt occurs, then save all the above data in a global buffer instead, so you can view it after reaching the while (1).
如果printf在这个特定的 Hard-Fault 中断发生的执行点上不能使用,那么把上面的所有数据都保存在一个全局缓冲区中,这样你就可以在到达while (1).
Then, refer to the 'Cortex-M Fault Exceptions and Registers' section at http://www.keil.com/appnotes/files/apnt209.pdfin order to understand the problem, or publish the output here if you want further assistance.
然后,请参阅http://www.keil.com/appnotes/files/apnt209.pdf上的“Cortex-M 故障异常和寄存器”部分以了解问题,如果需要进一步帮助,请在此处发布输出.
UPDATE:
更新:
In addition to all of the above, make sure that the base address of the heap is defined correctly. It is possibly hard-coded within the project settings (typically right after the data-section and the stack). But it can also be determined during runtime, at the initialization phase of your program. In general, you need to check the base addresses of the data-section and the stack of your program (in the map file created after building the project), and make sure that the heap does not overlap either one of them.
除了上述所有内容外,请确保正确定义了堆的基地址。它可能在项目设置中进行了硬编码(通常在数据部分和堆栈之后)。但它也可以在运行时确定,在程序的初始化阶段。通常,您需要检查数据部分的基地址和程序堆栈(在构建项目后创建的映射文件中),并确保堆不重叠其中任何一个。
I once had a case where the base address of the heap was set to a constant address, which was fine to begin with. But then I gradually increased the size of the data-section, by adding global variables to the program. The stack was located right after the data-section, and it "moved forward" as the data-section grew larger, so there were no problems with either one of them. But eventually, the heap was allocated "on top of" part of the stack. So at some point, heap-operations began to override variables on the stack, and stack-operations began to override the contents of the heap.
我曾经遇到过将堆的基地址设置为常量地址的情况,这很好。但是后来我通过向程序添加全局变量来逐渐增加数据部分的大小。堆栈位于数据部分之后,随着数据部分变大,它“向前移动”,因此它们中的任何一个都没有问题。但最终,堆被分配到堆栈的“顶部”部分。所以在某个时刻,堆操作开始覆盖堆栈上的变量,堆栈操作开始覆盖堆的内容。
回答by berendi - protesting
The arm-none-eabi-*toolchaindistribution includes the newlibC library. When newlib is configured for an embedded system, then the user program must provide an _sbrk()functionfor it to work properly.
的arm-none-eabi-*工具链分布包括newlibC库。当为嵌入式系统配置 newlib 时,用户程序必须提供一个_sbrk()函数才能使其正常工作。
malloc()relies solely on _sbrk()to figure out where the heap memory starts, and where it ends. The very first call to _sbrk()returns the start of the heap, and subsequent calls should return -1if the required amount of memory is not available, then malloc()would in turn return NULLto the application. Your _sbrk()looks broken, because it apparently lets you allocate more memory than there is available. You should be able to fix it so that it returns -1beforethe heap is expected to collide with the stack.
malloc()完全依赖于_sbrk()确定堆内存从哪里开始,从哪里结束。第一次调用_sbrk()返回堆的开始,如果所需的内存量不可用,后续调用应该返回-1,然后malloc()将返回NULL到应用程序。您_sbrk()看起来很糟糕,因为它显然可以让您分配比可用内存更多的内存。您应该能够修复它,以便它-1在预计堆与堆栈发生冲突之前返回。
回答by Jayesh Bhoi
Using standard c mallocit's very hard to distinguish and mallocis seems buggy from my view. So you can manage memory by implementing some custom mallocusing your RAM address.
使用标准c malloc很难区分,而且malloc在我看来似乎有问题。因此,您可以通过malloc使用您的 RAM 地址实现一些自定义来管理内存。
I am not sure may this help you but i have done some custom mallocin my controller related project it's as follows
我不确定这是否对您有帮助,但我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;
This basically macro defines for RAM address and have manually chose more block number for block size which frequently require to allocate,Like 36 bytes required me more so i take more number for it.
这个基本的宏定义了 RAM 地址,并为经常需要分配的块大小手动选择了更多的块号,比如 36 字节需要我更多,所以我为它取了更多的数字。
This is init function for mem init
这是 mem init 的 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;
}
This one for allocation
这一个用于分配
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;
}
This one for free
这个免费
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:;
}
After all you can use above function like
毕竟你可以使用上面的功能
void *mem=NULL;
mem=CUS_Malloc(wantedsize);
Then can also watch your used memory as follows
然后也可以如下查看你使用的内存
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");
}
In general have pre-calculated the memory first then give as i have.
一般来说,先预先计算内存,然后像我一样给出。
回答by K. Simon
Here you can find how I could "force" malloc() to return NULL, if the heap is too small for allocating based on berendi's previous answer. I estimated the maximum amount of STACK and based on this I could calculate the address where the stack can start in worst case.
在这里,您可以找到如何“强制” 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
}

