Stack 栈向下
heap 堆
unintialized data未初始化数据段
initialized data初始化数据段
text 代码段
补充:
栈空间和堆空间是指在计算机内存中用于存储数据和程序的两种不同的分配方式。
栈空间(Stack Space): 栈空间主要用于存储函数调用时的局部变量、函数参数以及函数调用过程中的一些上下文信息。 特点:
堆空间(Heap Space): 堆空间主要用于存储动态分配的内存,在程序运行过程中通过特定的函数(如malloc或new)手动申请和释放。 特点:
总结: 栈空间主要用于存储函数调用过程中的局部变量和上下文信息,大小固定,由编译器自动管理;堆空间主要用于动态分配内存,大小可调,需要手动申请和释放。栈空间的访问速度更快,而堆空间具有更大的灵活性。在编程中,正确管理栈空间和堆空间的使用至关重要,以避免内存泄漏、溢出和访问错误等问题。
未初始化 数据会清0,初始化数据段程序执行时只读。
程序出现bug时,可分析此文件。
没有用到的移除
镜像符号列表,.o文件放在哪个地址,大小多少。
全局
内存分布
整个代码内存分布,如果内存不够了,可以查看哪个文件占用内存最多
上述说明:
LR_IROM1 代表代码放到ROM中的大小
所有*.o 所有RO都可以放到ROM1
RW文件放到 RAM
这个文件主要的作用分散加载文件,可以指定heap、stack,或者外部ram在这个文件中修改加载
hex文件的组成:段大小、地址、长度、数据、校验。
.bin文件一般用于改参数,分析flash读写出现的问题等。
静态内存:
静态内存是指在程序开始运行时由编译器分配的内存,它的分配是在程序开始编译时完成的,不占用CPU资源。 程序中的各种变量,在编译时系统已经为其分配了所需的内存空间,当该变量在作用域内使用完毕时,系统会 自动释放所占用的内存空间。 变量的分配与释放,都无须程序员自行考虑。
动态内存:
动态内存分配是按输入信息的大小分配所需要的内存单元,他的特点是按需分配,内存分配在堆区。 用户无法确定空间大小,或者空间太大,栈上无法分配时,会采用动态内存分配。
区别:
calloc
是一个在 C 语言中使用的函数,用于动态内存分配。它用于在堆内存上分配指定数量的连续字节,并将每个字节都初始化为零。
其中,num
表示要分配的元素数量,size
表示每个元素的大小(以字节为单位)。calloc
函数会分配 num * size
字节的内存,并返回一个指向新分配内存起始位置的指针。如果分配失败,则返回空指针 NULL
。
与 malloc
函数不同,calloc
在分配内存后会自动将每个字节初始化为零。这对于需要清零内存的情况非常方便,例如用于存储数组或结构体等数据结构。
下面是一个使用 calloc
函数动态分配一个包含 5 个整数的数组的例子:
#include
#include
int main() {
int* array = calloc(5, sizeof(int));
if (array != NULL) {
for (int i = 0; i < 5; i++) {
printf("%d ", array[i]);
}
free(array); // 释放内存
}
return 0;
}
realloc
函数的语法如下:
其中,ptr
是指向之前通过 malloc
、calloc
或 realloc
分配的内存块的指针,size
是要重新分配的内存块的新大小(以字节为单位)。realloc
函数会尝试将之前分配的内存块的大小调整为新的大小,并返回一个指向重新分配后内存块起始位置的指针。如果分配失败,则返回空指针 NULL
。
需要注意的是,realloc
在重新分配内存时可能会进行数据的复制和移动。如果旧的内存块可以满足新的大小,那么就会直接扩展或缩小原来的内存块。否则,realloc
会在其他地方重新分配一块新的内存,并将原先内存块中的数据复制到新的内存块中。
下面是一个使用 realloc
函数重新分配一个整数数组内存大小的例子:
#include
#include
int main() {
int* array = malloc(5 * sizeof(int)); // 初始分配 5 个整数的内存
if (array != NULL) {
// 使用内存...
// 重新分配内存大小为 10 个整数
int* new_array = realloc(array, 10 * sizeof(int));
if (new_array != NULL) {
array = new_array; // 更新指针
// 使用重新分配后的内存...
free(array); // 释放内存
}
}
return 0;
}
这些函数在小型嵌入式系统中并不总是可用的,小型嵌入式设备中的 RAM 不足。
它们的实现可能非常的大,占据了相当大的一块代码空间。
他们几乎都不是安全的。
它们并不是确定的,每次调用这些函数执行的时间可能都不一样。
它们有可能产生碎片。
这两个函数会使得链接器配置得复杂。
如果允许堆空间的生长方向覆盖其他变量占据的内存,它们会成为 debug 的灾难。
如果用malloc,当80B和10B释放掉,100B成为了内存碎片,下面看下FreeRTOS怎么解决内存分配
heap1:只能分配不能释放,适合不可能去删除的任务
heap2:支持分配也支持释放,但是有内存碎片,可用空间会越来越少。释放的空间无法用。
heap3:在heap2的基础上多了任务保护,类似锁,不会被打断
heap4:有分配,有释放,有管理(默认)
heap5:内部其实也是heap4,有内部ram也可以扩展外部ram。音视频会占用大量内存。
申请、释放,获取空闲内存
修改按键代码,每个按键按下的时候都去处理
/* USER CODE END Header_Delay_Task */
void Delay_Task(void const * argument)
{
/* USER CODE BEGIN Delay_Task */
EventBits_t KeyEventBits;
uint8_t *HeapBuf = NULL;
/* Infinite loop */
for(;;)
{
/*
1、修改事件标志组等待状态为 触发后清除标志
2、检测按键,处理相关功能
*/
KeyEventBits = xEventGroupWaitBits(KeyEventGroup,
KEY3_EVENT_BIT|KEY4_EVENT_BIT|KEY5_EVENT_BIT|KEY6_EVENT_BIT,
pdTRUE,
pdFALSE,
portMAX_DELAY);
printf("Key is Down Key Event Bit is %x\r\n",KeyEventBits);
switch(KeyEventBits){
case KEY3_EVENT_BIT:
if(HeapBuf == NULL){
HeapBuf = pvPortMalloc(100);
}
else{
printf("plese press K4 free!\r\n");
}
break;
case KEY4_EVENT_BIT:
if(HeapBuf != NULL){
vPortFree(HeapBuf);
HeapBuf = NULL;
}
else{
printf("plese press K3 Malloc!\r\n");
}
break;
case KEY5_EVENT_BIT:
printf("Heap Free Size is%d\r\n",xPortGetFreeHeapSize());
break;
default:
break;
}
osDelay(10);
}
/* USER CODE END Delay_Task */
}
效果
第一次申请空间是12152
第二次申请空间是12264
申请100字节,多出了12字节,为什么?下面分析原理。
通用的内存管理需求:
特点:
首先了解控制块
/*
内存堆初始化
1、宏定义
#if portBYTE_ALIGNMENT == 8 对齐字节数
#define portBYTE_ALIGNMENT_MASK ( 0x0007 ) 对齐掩码
#endif
2、内存块链表节点长度
这里保证可被8整除,内存操作效率高
static const size_t xHeapStructSize = ( sizeof( BlockLink_t ) + ( ( size_t ) ( portBYTE_ALIGNMENT - 1 ) ) ) & ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
*/
static void prvHeapInit( void )
{
BlockLink_t *pxFirstFreeBlock;
uint8_t *pucAlignedHeap;
size_t uxAddress;
size_t xTotalHeapSize = configTOTAL_HEAP_SIZE;
/* 获取内存堆数组首地址 */
uxAddress = ( size_t ) ucHeap;
//当addr 后三位不等于0,就需要字节对齐
//不能被8整除,8字节对齐,就是能够被8整除
if( ( uxAddress & portBYTE_ALIGNMENT_MASK ) != 0 )
{
//uxAddress = uxAddress +7???
uxAddress += ( portBYTE_ALIGNMENT - 1 );
//把后三位变成0,保证在8字节对齐上
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
//获取了总可用长度,由低地址到高地址增长(对齐后地址减去数组地址)
xTotalHeapSize -= uxAddress - ( size_t ) ucHeap;
}
//对齐后的首地址
pucAlignedHeap = ( uint8_t * ) uxAddress;
/*
初始化了头结点xStart
下一个可用空闲块为对齐后的首地址
头结点的内存大小为0
解析:
xStart 分配在全局内存中
不用于存储块记录,只用链表操作查找用
*/
xStart.pxNextFreeBlock = ( void * ) pucAlignedHeap;
xStart.xBlockSize = ( size_t ) 0;
/*
初始化尾节点
1、获取整个内存堆尾地址
2、减去一个链表节点长度
3、再去保证地址在8字节对齐上
4、把尾节点进行赋值
5、尾节点下一个指向NULL
*/
uxAddress = ( ( size_t ) pucAlignedHeap ) + xTotalHeapSize;
uxAddress -= xHeapStructSize;
uxAddress &= ~( ( size_t ) portBYTE_ALIGNMENT_MASK );
pxEnd = ( void * ) uxAddress;
pxEnd->xBlockSize = 0;
pxEnd->pxNextFreeBlock = NULL;
/*
初始化内存堆的第一个空闲块
1、首地址为内存堆可用空闲首地址
2、减去尾地址,获取到可用空间大小
3、下个指向尾节点
*/
pxFirstFreeBlock = ( void * ) pucAlignedHeap;
pxFirstFreeBlock->xBlockSize = uxAddress - ( size_t ) pxFirstFreeBlock;
pxFirstFreeBlock->pxNextFreeBlock = pxEnd;
/* 更新剩余内存信息 */
xMinimumEverFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
xFreeBytesRemaining = pxFirstFreeBlock->xBlockSize;
/*标志位置位,32Bit 最高位为1 主要用于判断内存块类型使用*/
xBlockAllocatedBit = ( ( size_t ) 1 ) << ( ( sizeof( size_t ) * heapBITS_PER_BYTE ) - 1 );
}
初始化的概述图
源码分析
/*
内存块申请
根据传入大小,返回内存块指针,无可用空间返回NULL
1、全局变量
static BlockLink_t xStart, *pxEnd = NULL;//赋值时就为空
*/
void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;
//挂起调度器
vTaskSuspendAll();
{
/* */
if( pxEnd == NULL )
{
//触发内存堆的初始化
prvHeapInit();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/*
1、最高位用于确定谁拥有区块-应用程序还是内核,所以它必须是0。
*/
if( ( xWantedSize & xBlockAllocatedBit ) == 0 )
{
/* */
if( xWantedSize > 0 )
{
//加上链表节点长度,这也就是解释了 我们申请了100字节,但是实际占用了112,为什么多出了12个字节
xWantedSize += xHeapStructSize;
/* 保证能被8整除 */
if( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) != 0x00 )
{
/* Byte alignment required. */
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
configASSERT( ( xWantedSize & portBYTE_ALIGNMENT_MASK ) == 0 );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//是否有可用空闲块
if( ( xWantedSize > 0 ) && ( xWantedSize <= xFreeBytesRemaining ) )
{
/*
查找可用空闲块
单向链表,先从头结点开始
1、pxPreviousBlock 开始遍历
遍历结束条件 大小满足或者没有空闲块了
*/
pxPreviousBlock = &xStart;
pxBlock = xStart.pxNextFreeBlock;
while( ( pxBlock->xBlockSize < xWantedSize ) && ( pxBlock->pxNextFreeBlock != NULL ) )
{
pxPreviousBlock = pxBlock;
pxBlock = pxBlock->pxNextFreeBlock;
}
/* 是否有空闲块 */
if( pxBlock != pxEnd )
{
/* 获取分配的内存首地址(块的首地址+节点长度) */
pvReturn = ( void * ) ( ( ( uint8_t * ) pxPreviousBlock->pxNextFreeBlock ) + xHeapStructSize );
/* 删除 已经分配的节点*/
pxPreviousBlock->pxNextFreeBlock = pxBlock->pxNextFreeBlock;
/* If the block is larger than required it can be split into
two. */
if( ( pxBlock->xBlockSize - xWantedSize ) > heapMINIMUM_BLOCK_SIZE )
{
/*获取剩余的空闲首地址*/
pxNewBlockLink = ( void * ) ( ( ( uint8_t * ) pxBlock ) + xWantedSize );
configASSERT( ( ( ( size_t ) pxNewBlockLink ) & portBYTE_ALIGNMENT_MASK ) == 0 );
/* 计算剩余长度 */
pxNewBlockLink->xBlockSize = pxBlock->xBlockSize - xWantedSize;
pxBlock->xBlockSize = xWantedSize;
/* 插入到空闲链表中去 */
prvInsertBlockIntoFreeList( pxNewBlockLink );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//更新我们的剩余空闲大小
xFreeBytesRemaining -= pxBlock->xBlockSize;
if( xFreeBytesRemaining < xMinimumEverFreeBytesRemaining )
{
xMinimumEverFreeBytesRemaining = xFreeBytesRemaining;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 将分配出去的内存块,进行标记 */
pxBlock->xBlockSize |= xBlockAllocatedBit;
pxBlock->pxNextFreeBlock = NULL;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
traceMALLOC( pvReturn, xWantedSize );
}
//恢复调度器
( void ) xTaskResumeAll();
configASSERT( ( ( ( size_t ) pvReturn ) & ( size_t ) portBYTE_ALIGNMENT_MASK ) == 0 );
return pvReturn;
}
3中的问题:申请100字节,多出了12字节,为什么?
xWantedSize += xHeapStructSize 加上链表节点长度,这也就是解释了 我们申请了100字节,但是实际占用了112,为什么多出了12个字节
1先找到内存卡
2判断首地址和尾地址是否一样,一样合并
3再判断下一个首地址和尾地址是否一样,一样合并更新
/*
把内存块插入到空闲内存块中
*/
static void prvInsertBlockIntoFreeList( BlockLink_t *pxBlockToInsert )
{
BlockLink_t *pxIterator;
uint8_t *puc;
/*
找到pxBlockToInsert位置
pxIterator->pxNextFreeBlock > pxBlockToInsert 表示已经找到
之后pxIterator地址在pxBlockToInsert实际物理地址之前
*/
for( pxIterator = &xStart; pxIterator->pxNextFreeBlock < pxBlockToInsert; pxIterator = pxIterator->pxNextFreeBlock )
{
}
/*
判断是否可以合并 (向上合并)
1、内存块长度进行累加
2、要插入的地址,变成合并后的地址
*/
puc = ( uint8_t * ) pxIterator;
if( ( puc + pxIterator->xBlockSize ) == ( uint8_t * ) pxBlockToInsert )
{
pxIterator->xBlockSize += pxBlockToInsert->xBlockSize;
pxBlockToInsert = pxIterator;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/*
判断是否可以合并 (向下合并)
1、再判断是否为尾节点
2、内存块长度进行累加
3、把要合并的内存块从空闲链表中移除
*/
puc = ( uint8_t * ) pxBlockToInsert;
if( ( puc + pxBlockToInsert->xBlockSize ) == ( uint8_t * ) pxIterator->pxNextFreeBlock )
{
if( pxIterator->pxNextFreeBlock != pxEnd )
{
/* Form one big block from the two blocks. */
pxBlockToInsert->xBlockSize += pxIterator->pxNextFreeBlock->xBlockSize;
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock->pxNextFreeBlock;
}
else
{
pxBlockToInsert->pxNextFreeBlock = pxEnd;
}
}
/*
1、这里没有向下合并操作,直接插入就可以,连接到空闲链表中
*/
else
{
pxBlockToInsert->pxNextFreeBlock = pxIterator->pxNextFreeBlock;
}
/*
1、这里没有向上合并操作,直接插入就可以,连接到空闲链表中
*/
if( pxIterator != pxBlockToInsert )
{
pxIterator->pxNextFreeBlock = pxBlockToInsert;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/*
释放内存块
参数:传入要释放的内存块地址
*/
void vPortFree( void *pv )
{
uint8_t *puc = ( uint8_t * ) pv;
BlockLink_t *pxLink;
//判断内存块有效
if( pv != NULL )
{
/*获取传入内存块的节点地址 */
puc -= xHeapStructSize;
pxLink = ( void * ) puc;
//判断最高位为1
if( ( pxLink->xBlockSize & xBlockAllocatedBit ) != 0 )
{
//下个节点为NULL
if( pxLink->pxNextFreeBlock == NULL )
{
/*最高位清除置位为0 */
pxLink->xBlockSize &= ~xBlockAllocatedBit;
//挂起调度器
vTaskSuspendAll();
{
/* 更新剩余空间大小 */
xFreeBytesRemaining += pxLink->xBlockSize;
//插入到空闲链表中去
prvInsertBlockIntoFreeList( ( ( BlockLink_t * ) pxLink ) );
}
//恢复调度器
( void ) xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}