前几天参加了某外企二面,项目讲完没继续对着质询,上来就问了一道关于嵌入式系统堆栈段分配的问题。当时就已经知道这把又要塔西狼......所以今天赶紧查资料看网课,总算是把这块基础理论补齐了。
在嵌入式系统中,内存管理和程序结构与Windows系统类似,但由于资源受限(如内存容量小、处理器性能低),需要更精细的优化。
嵌入式系统的内存分段规则如下表所示:
低地址 | Code Segment(代码段) | .text 程序代码 |
| | Const Segement (常量段) | ,rodata 字面常量,字符串等 |
| | Global/Static Segement (全局/静态段) |
声明未初始化的放 .bass 声明且初始化0放.bass 声明且初始化的放 .data |
| |
Heap Segment(堆段) 向下增长 | 由编写代码的人分配内存释放(malloc&&free) |
高地址 |
Stack Segment(栈段) 变量先定义放高地址,bit先出现(高位)放高地址,向上增长 (小端序) | 由编译器自动分配释放,存放局部变量、形参、返回值 |
1. 代码段
程序可执行代码存放的位置,详细包括:
(1)编译后的机器指令
(2)字符串常量和define的常量
(3)程序正在执行的代码
物理位置:flash(闪存)中
2. 常量段
程序中RO(只读)数据存放的位置
(1)字符串、数字等常量存放在常量段
(2)const修饰的全局变量存放在常量段
(3)程序运行中此段的内容不可以被修改
物理位置:flash中,嵌入式系统中所有RO的数据都不会存放在SRAM中
3. 全局/静态段
.bass :
(1)未初始化的全局变量和未初始化的静态变量存放在.bss段。
(2)初始化为0的全局变量和初始化为0的静态变量存放在.bss段。
(3)bss段不占用exe空间,其内容由操作系统初始化。
物理位置:SRAM(静态存储器)与计算机系统中的DRAM不同,SRAM不需要间断刷新电容来维持存储的内容。
.data:
(1) 已初始化的全局/静态变量存放在.data段
(2)data段占用exe空间,内容由代码初始化生成
物理位置: SRAM和flash
4. 堆段
此部分内存由开发者自由开辟,但是存在一个隐式指针不断地从低地址向高地址移动,所以Heap Segment的内存地址应该是从低到高增长的。此外,malloc自由开辟的内存使用完一定要用free去释放,否则会内存泄漏。
5. 栈段
什么时候会用到栈段?
(1)局部变量和const定义的局部变量
(2)入口参数和返回值(所以在一些算法题中,我们将返回数组视为不占内存)
特点:
(1)由系统自动管理,无需手动开辟释放
(2)定义在栈段的变量,离开作用域后即被释放
(3)由高地址向低地址增长(这个好理解,微机原理里面遇到过很多类似),并且与数据结构中的栈结构进出规律相同,都是FIRST IN LAST OUT(FILO)
(4)使用起来速度快,但是没有自由度,最大空间较少
接下来分享一道例题:
//某函数内有
char szStr1[] = {'1', '0', '0', "\0', 'a', "\0', '0'};
char szStr2[6]= {'1', '0', '0', '0', '0', '0'};
//请问
strlen(szStr1)=???
sizeof(szStr1)=???
strlen(szStr2)=???
sizeof(szStr2)=???
第一问strlen遍历到\0停止,所以求出来长度是3
第二问Str1占了7Byte,所以答案是7
第三问......肯定有人会冲动的填6
这个题确实6,把博客里一大半知识点利用了。
我们重新从题干开始分析,首先这两行数组初始化是在某个函数中进行的,所以是局部变量。因此,接下来的分析应该全部围绕Stack Segment展开。
首先我们做了:
char szStr1[] = {'1', '0', '0', '\0', 'a', '\0', '0'};
所以,此行被执行后,栈段情况应该是;
1 | 低地址 |
0 | |
0 | |
\0 | |
a | |
\0 | |
0 | 高地址 |
接着,我们压入:
char szStr2[6]= {'1', '0', '0', '0', '0', '0'};
这下子内存结构就变成了:
1 | szStr2 |
0 | |
0 | |
0 | |
0 | |
0 | |
1 | szStr1 |
0 | |
0 | |
\0 | |
a | |
\0 | |
0 | 高地址方向 |
这个时候我们对Str2做strlen,遍历指针到了szStr2表尾发现没有\0,它会继续向下遍历到szStr1里的\0前才停止。
所以得出来的字符串长度是9