简单理解堆区栈区
在 ARM 系统中,堆(Heap)和栈(Stack)是两种不同的内存管理区域,各自有独特的用途和特点。以下是它们的关系和区别:
栈(Stack)
- 结构:栈是一种后进先出(LIFO)的数据结构。最近压入的数据会最先弹出。
- 用途:
- 用于存储局部变量、函数参数和返回地址。
- 在函数调用时,每个函数会分配一个栈帧,用于保存临时数据。
- 管理:
- 由编译器自动管理,函数调用时分配空间,返回时释放空间。
- 栈的大小通常较小,受限于操作系统的配置。
- 速度:栈的分配和释放速度非常快,因为只需调整栈指针。
堆(Heap)
- 结构:堆是一种动态内存管理区域,可以随机访问。
- 用途:
- 用于存储动态分配的内存,比如通过
malloc
、calloc
等函数分配的内存。 - 适用于需要在运行时分配和释放的较大或不确定大小的数据。
- 用于存储动态分配的内存,比如通过
- 管理:
- 由程序员手动管理,分配后必须显式释放,若未释放可能会导致内存泄漏。
- 堆的大小通常比栈大,可以动态增长,直到系统内存耗尽。
- 速度:相对于栈,堆的分配和释放速度较慢,因为需要查找合适的空闲块。
栈与堆的关系
- 内存区域:在内存中,栈和堆通常位于不同的区域。栈从高地址向低地址增长,而堆从低地址向高地址增长,这样可以有效利用内存。
- 协作:在程序执行过程中,栈和堆常常一起工作。函数使用栈来管理局部变量和调用信息,而需要动态存储的数据则使用堆。
- 内存安全:不当的堆和栈使用可能导致内存安全问题(如栈溢出、堆溢出等),因此需要谨慎管理。
总结
在 ARM 系统中,栈和堆各自承担着重要的角色,栈用于快速管理局部数据,堆则用于处理动态数据。合理地使用这两种内存管理方式能够提高程序性能和稳定性。
堆区
初始化一个堆区
堆空间的结构
对应结构图
对应代码
分配两个内存空间
char *str1=my_malloc(100);
char *str2=my_malloc(50);void *my_malloc(int size)
{int old_pos = pos;pos += size;return &heap_buf[old_pos];
}
初始化空堆
注意
在 ARM 架构中,堆的分配和初始化通常需要分配一个空的空间,这主要是出于以下几个原因:
1. 内存对齐
- 对齐要求:许多处理器架构(包括 ARM)要求数据在内存中的地址必须是特定大小的倍数。例如,32 位数据需要在 4 字节对齐,64 位数据需要在 8 字节对齐。分配一个空的空间可以确保下一个分配的内存区域满足这些对齐要求。
2. 管理元数据
- 元数据存储:动态内存分配库(如
malloc
和free
)通常会在分配的内存块前或后存储一些元数据,以跟踪每个块的大小和状态(如是否已分配)。分配一个空的空间可以用于存储这些元数据,帮助正确地管理内存。
3. 避免碎片化
- 减少内存碎片:通过预留一定的空间,可以更好地管理内存的分配和释放,从而降低内存碎片化的风险。当释放内存时,可以将相邻的空闲块合并,从而形成更大的可用内存块。
4. 初始状态检查
- 安全性:分配一个空的空间可以作为初始化标识,帮助检查使用未初始化内存的情况。某些分配器会将这段空的空间设置为特定的值(如
0xDEADBEEF
),以便在调试时快速识别未初始化的内存访问。
5. 动态扩展
- 扩展能力:当堆需要扩大时,分配一个空的空间可以作为扩展的基础,允许动态内存管理器在必要时向操作系统请求更多内存,而不影响现有的内存分配逻辑。
总结
在 ARM 架构中,堆的分配和初始化过程中分配一个空的空间,有助于满足对齐要求、管理元数据、减少碎片化、提高安全性以及支持动态扩展。这些措施共同确保了内存管理的高效性和可靠性。
初始化视图
分配一个空间
释放空间之后怎么利用被释放的空间
(1)分配两个空间
(2)释放一个空间堆区变化
(3)关键
如果第一块的剩余空间,不足以分配下一个所要分配的空间,那么就会进入下一个剩余空间,以此类推,如果都不行,则分配失败。
栈区
创建代码
char heap_buf[1024];
int pos = 0;
int g_cnt=0;
int b_func(volatile int a){a+=2;return a;
}
int c_func(volatile int a){a+=3;return a;
}
void *my_malloc(int size)
{int old_pos = pos;pos += size;return &heap_buf[old_pos];
}void my_free(void *buf)
{/* err */
}
void a_func(volatile int a){g_cnt=b_func(a);g_cnt=c_func(g_cnt);
}int main(void)
{char ch = 65; // char ch = 'A';volatile int i=99;char *buf = my_malloc(100);unsigned char uch = 200;a_func(i);return 0;
}
生成反汇编码
$ti.a_funca_func0x08000154: b501 .. PUSH {r0,lr}0x08000156: 9800 .. LDR r0,[sp,#0]0x08000158: f000f80c .... BL b_func ; 0x8000174/*LR=0x0800015c;PC=(b_func)0x08000174 */0x0800015c: 4904 .I LDR r1,[pc,#16] ; [0x8000170] =0x200000040x0800015e: 6008 .` STR r0,[r1,#0]0x08000160: 4608 .F MOV r0,r10x08000162: 6800 .h LDR r0,[r0,#0]0x08000164: f000f80c .... BL c_func ; 0x8000180/*LR=0x08000168;PC=(c_func)0x08000174 */0x08000168: 4901 .I LDR r1,[pc,#4] ; [0x8000170] = 0x200000040x0800016a: 6008 .` STR r0,[r1,#0]0x0800016c: bd08 .. POP {r3,pc}$d0x0800016e: 0000 .. DCW 00x08000170: 20000004 ... DCD 536870916$ti.b_funcb_func0x08000174: b501 .. PUSH {r0,lr}0x08000176: 9800 .. LDR r0,[sp,#0]0x08000178: 1c80 .. ADDS r0,r0,#20x0800017a: 9000 .. STR r0,[sp,#0]0x0800017c: 9800 .. LDR r0,[sp,#0]0x0800017e: bd08 .. POP {r3,pc}i.c_funcc_func0x08000180: b501 .. PUSH {r0,lr}0x08000182: 9800 .. LDR r0,[sp,#0]0x08000184: 1cc0 .. ADDS r0,r0,#30x08000186: 9000 .. STR r0,[sp,#0]0x08000188: 9800 .. LDR r0,[sp,#0]0x0800018a: bd08 .. POP {r3,pc}i.mainmain0x0800018c: b578 x. PUSH {r3-r6,lr}0x0800018e: 2441 A$ MOVS r4,#0x410x08000190: 2063 c MOVS r0,#0x630x08000192: 9000 .. STR r0,[sp,#0]0x08000194: 2064 d MOVS r0,#0x640x08000196: f000f807 .... BL my_malloc ; 0x80001a80x0800019a: 4606 .F MOV r6,r00x0800019c: 25c8 .% MOVS r5,#0xc80x0800019e: 9800 .. LDR r0,[sp,#0]0x080001a0: f7ffffd8 .... BL a_func ; 0x8000154/*LR=0x080001a4;PC=(a_func)0x08000154 */0x080001a4: 2000 . MOVS r0,#00x080001a6: bd78 x. POP {r3-r6,pc}i.my_mallocmy_malloc0x080001a8: 4601 .F MOV r1,r00x080001aa: 4804 .H LDR r0,[pc,#16] ; [0x80001bc] = 0x200000000x080001ac: 6802 .h LDR r2,[r0,#0]0x080001ae: 6800 .h LDR r0,[r0,#0]0x080001b0: 4408 .D ADD r0,r0,r10x080001b2: 4b02 .K LDR r3,[pc,#8] ; [0x80001bc] = 0x200000000x080001b4: 6018 .` STR r0,[r3,#0]0x080001b6: 4802 .H LDR r0,[pc,#8] ; [0x80001c0] = 0x200000080x080001b8: 4410 .D ADD r0,r0,r20x080001ba: 4770 pG BX lr$d0x080001bc: 20000000 ... DCD 5368709120x080001c0: 20000008 ... DCD 536870920
提问
1.LR被覆盖了怎么办?
在汇编码中在a_func b_func c_func开始
0x08000154: b501 .. PUSH {r0,lr}
0x08000174: b501 .. PUSH {r0,lr}
0x08000180: b501 .. PUSH {r0,lr}
将r0,LR压入栈中,减少栈区空间,实现保存值的功能
2.局部变量在栈中分配,如何分配?
局部变量在寄存器以及内存的栈中分配
volatile
volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
volatile加与不加对汇编语言的影响
加volatile
加volatile代码变量不会进行对栈区进行优化,只会老老实实返还到栈区
汇编代码
0x0800018e: 2441 A$ MOVS r4,#0x410x08000190: 2063 c MOVS r0,#0x63
c语言代码
volatile int i=99;
不加volatile
不加volatile代码变量会对代码进行优化,直接把数据放入CPU的寄存器中,这样访问速度加快
汇编代码
0x0800018e: 2441 A$ MOVS r4,#0x41
c语言代码
char ch = 65;
3.为什么每个RTOS任务都有自己的栈
同时执行两个任务如果在执行A任务中需要执行任务B,此时计算机不知道操作者在任务A中哪个数据有用,那么就必须保存现场,把A中的数据存放在栈区,去执行另一个栈的任务
堆栈它们不是同一个东西
⚫ 堆, heap ,就是一块空闲的内存,需要提供管理函数◼ malloc :从堆里划出一块空间给程序使用◼ free :用完后,再把它标记为 " 空闲 " 的,可以再次使用⚫ 栈, stack ,函数调用时局部变量保存在栈中,当前程序的环境也是保存在栈中◼ 可以从堆中分配一块空间用作栈