最后一张图可以总结全部内容
局部变量:
-
栈空间的准备
-
栈的初始化:在程序启动时,系统会为程序分配一段连续的内存区域作为栈空间,并且设置栈指针
SP
(Stack Pointer)。在 ARM 架构中,栈通常是 “满递减栈”(栈向低地址增长,且 SP 指向当前栈顶的有效数据,先移动 SP,再存数据),即栈指针SP
初始时指向栈空间的高地址,随着数据入栈,SP
的值逐渐减小。
-
函数调用时的准备:当一个函数被调用时,首先会将当前的程序状态(如链接寄存器
LR
,它保存了函数调用结束后要返回的地址 )和一些需要保护的寄存器值压入栈中。例如,在汇编代码中会有类似PUSH {r2, r3, lr}
的指令,这一步是为了在函数执行过程中不破坏调用者的寄存器状态,同时记录函数返回地址。
-
局部变量的分配
-
计算局部变量所需空间:编译器会根据函数中定义的局部变量类型和数量,计算出这些局部变量总共需要占用的字节数。比如函数中定义了两个
int
类型的局部变量,在 32 位 ARM 架构下,int
通常占 4 个字节,那么这两个局部变量共需 8 个字节。 -
分配栈空间:计算好空间后,通过调整栈指针
SP
的值来为局部变量分配空间。具体做法是让SP
减去局部变量所需的字节数。例如,如果局部变量需要 8 个字节,那么执行SUBS SP, SP, #8
指令,此时栈指针指向新的位置,这部分新的栈空间就用于存储局部变量。
-
局部变量的使用
-
访问局部变量:在函数执行过程中,通过相对于栈指针
SP
的偏移量来访问局部变量。例如,当局部变量分配好后,第一个局部变量可能相对于SP
的偏移是 0,第二个局部变量相对于SP
的偏移是 4(假设是 32 位int
类型) ,使用类似LDR R0, [SP, #0]
的指令从栈中读取第一个局部变量的值到寄存器R0
。
-
修改局部变量:对局部变量进行赋值等操作时,将数据写入对应的栈空间位置。比如执行
STR R1, [SP, #0]
指令,将寄存器R1
中的值写入第一个局部变量所在的栈空间位置。
-
局部变量的释放
-
函数返回时的操作:当函数执行完毕准备返回时,需要恢复之前保存的寄存器状态和栈指针。通过
POP
指令,如POP {r2, r3, pc}
,不仅可以恢复r2
、r3
等寄存器的值,还会将栈指针SP
恢复到函数调用前的位置,相当于释放了为局部变量分配的栈空间。因为栈指针回到了原来的位置,这部分局部变量占用的栈空间就可以被后续的函数调用重新使用。
暂时无法在飞书文档外展示此内容
全局变量
-
程序内存布局基础
ARM 架构下的程序在运行时,内存通常被划分为多个区域,常见的有代码段(.text)、数据段(.data)、BSS 段(.bss)、堆(Heap)和栈(Stack) 。
-
代码段:存放程序的可执行代码。
-
数据段:存放已经初始化且值不为 0 的全局变量和静态变量。
-
BSS 段:存放未初始化的全局变量和初始化为 0 的静态变量。
-
堆:用于动态内存分配,由程序员通过
malloc
等函数申请和释放。 -
栈:如前文所述,用于局部变量、函数参数传递、函数返回地址存储等
-
静态局部变量也就是加了static
-
已初始化全局变量的分配
-
编译期处理:在程序编译链接阶段,编译器会扫描代码中已初始化且值不为 0 的全局变量,为它们分配固定的内存地址。这些变量会被放置在数据段(.data)中。
-
运行期加载:当程序被加载到内存运行时,操作系统会从可执行文件中读取数据段的内容,将已初始化的全局变量的值加载到对应的内存位置。例如,定义一个全局变量
int global_var = 10;
,在编译时编译器会为global_var
在数据段分配一个地址,运行时操作系统将数值10
写入该地址对应的内存单元。
-
未初始化全局变量的分配
-
编译期标记:对于未初始化的全局变量,编译器会将它们标记出来,在链接阶段把它们集中放置在 BSS 段。比如
int global_uninit_var;
,编译器只记录它是一个未初始化的全局变量。 -
运行期初始化:在程序启动时,操作系统会自动将 BSS 段的内存区域清零,从而实现对未初始化全局变量的隐式初始化(初始化为 0) 。这是因为 BSS 段只记录变量的存在,不占用额外的磁盘空间存储变量值,在运行时统一清零更高效。
-
全局变量的访问方式
-
直接寻址:ARM 汇编中,通过全局变量的绝对地址来访问它们。编译器会在生成汇编代码时,确定全局变量的地址。例如,假设全局变量
global_var
的地址是0x20001000
,可以使用LDR R0, =0x20001000
(获取地址到寄存器R0
),然后LDR R1, [R0]
(从该地址读取全局变量的值到寄存器R1
)来读取其值。 -
基于链接寄存器(LR)的相对寻址:也可以通过相对于链接寄存器(LR)的偏移来间接访问全局变量,这种方式在一些复杂的程序结构中有助于保持代码的灵活性和可重定位性。
-
与局部变量分配的区别
-
存储区域不同:局部变量存储在栈中,随着函数的调用和返回,栈空间动态变化;而全局变量存储在数据段或 BSS 段,其生命周期贯穿程序的整个运行过程,在程序启动时分配,程序结束时释放。
-
作用域不同:局部变量的作用域仅限于定义它的函数内部;全局变量的作用域可以是整个源文件,甚至通过
extern
关键字扩展到其他源文件。 -
分配时机不同:局部变量在函数调用时在栈上动态分配,函数返回时释放;全局变量在编译链接阶段确定内存位置,程序启动时完成分配和初始化(如果需要) 。