ARM中栈的种类与运用

文章介绍了栈的基本概念,作为程序运行时保存临时数据的内存区域,特别是其后进先出的特性。在C语言中栈由编译器自动管理,而在ARM底层,栈的使用涉及到栈指针和不同的栈操作,如增栈、减栈、满栈和空栈。文中详细阐述了满减栈(FD)的使用,以及如何通过STMFD和LDMFD指令在函数调用中保存和恢复现场。此外,还通过例子展示了叶子函数和非叶子函数的栈管理。
摘要由CSDN通过智能技术生成

1. 栈的概念

栈,本身是一段内存,程序运行时用于保存一些临时数据,如局部变量、参数、返回地址等等。

        学习了数据结构,对栈的概念相信大家都不陌生,后进先出的数据结构,即最后进栈的元素最先出栈。但是在C语言中,栈是由编译器自动管理的。这一点与堆相反,堆是由程序员手动分配和释放的,例如malloc和free。而栈是由编译器自动管理的。

        当程序调用一个函数时,编译器会为该函数的局部变量参数返回地址等信息在栈上分配一段空间,并将这些信息压入栈中。当函数执行完毕后,这些信息会被弹出栈,并回到调用该函数的位置继续执行代码。

在C语言中我们不需要了解栈的工作方式,但是学习了ARM底层架构,就要了解栈在程序中的工作过程。

2. 栈的分类

在学习数据结构,没有听说过栈还有什么分类,通常是将栈和队列一起讲的,而在底层技术中,栈其实有很多种分类,这些分类决定了栈的特点和使用方式,而C语言中不需要关心,所以我们没有了解。那么栈有哪几种分类呢?他们之间的区别是什么?

首先我们要了解,CPU要使用栈,那么就要知道栈的地址在哪?栈是由栈指针来管理的,栈指针指向栈的顶部。栈指针的地址通常由编译器或操作系统来分配,对于ARM来讲,在汇编语言中,可以使用寄存器SP来表示栈指针,所以当使用栈时,找到SP就能知道栈指针指向的地址了。

2.1 增栈与减栈

增栈:压栈操作将数据放入栈顶,栈顶指针向下移动;减栈:压栈操作将栈顶数据弹出,栈顶指针向上移动。

  • 对于增栈,存数据时,SP指向的地址越来越大;取数据时,SP指向的地址越来越小
  • 对于减栈,存数据时,SP指向的地址越来越小;取数据时,SP指向的地址越来越大

可以理解为SP最开始指向中间某个地址,增栈意味着向地址大的压入,而减栈意味着向地址小的压入。

2.2 满栈与空栈

满栈:栈指针指向最后一次压入到栈中的数据,压栈时需要先移动栈指针到相邻的位置再压栈

空栈:栈指针指向最后一次压入到栈中的数据的相邻位置,压栈时可直接压栈,之后才需要把栈指针移动到相邻位置

简单来说,

  • 对于满栈,刚开始的时候栈指针指向的地址有元素,指向的是最后一个压入栈中的数据,所以先移动栈指针,才能继续压栈。
  • 对于空栈,刚开始的时候栈指针指向的地址无元素,指向的是最后一个压入栈的数据的后一个地址,所以可以先进行压栈,后再移动栈指针。
2.3 四种栈的分类

根据2.1和2.2,我们可以知道栈的两种地址类型和两种数据方式,将这两种大类进行合并,我们可以知道,栈有四种类型:空增(EA)、空减(ED)、满增(FA)、满减(FD)对于ARM处理器一般使用满减栈

2.4 多寄存器内存访问指令的寻址方式

在寄存器中,用于将多个寄存器的数据存储到连续的一块内存中

STMIA和STMIB

这两种都是从低地址向高地址存放数据

  • STMIA:指针指向哪,就从那开始以由低到高地址存数据
  • STMIB:指针先指向的下一个地方开始,再由低到高地址存数据

STMDA和STMDB

这两种都是从高地址向低地址存放数据

  • STMDA:指针指向哪,就从那开始以由高到低地址存数据
  • STMDB:指针先指向的下一个地方开始,再由高到低地址存数据

思考:如上我们知道,ARM存储器一般使用满减栈,那么我们如何选择上述寻址方式?

回忆一下满减(FD),对于满减是先移动指针,再进行存入数据,且存入数据时,地址越来越小,可以很容易得到,满减对应的是STMDB

2.5 如何读取栈里的数据?

对于满减,后进先出,那么我们从地址低的读到地址高的,且从指针的指向开始,那么满减对应的应该是LADIA

示例代码:

	MOV R1, #1
	MOV R2, #2
	MOV R3, #3
	MOV R4, #4
	MOV R11, #0x40000020
	STMDB R11!, {R1-R4}
	LDMIA R11!, {R6-R9}
	@ STMFD R11!, {R1-R4}
	@ LDMFD R11!, {R6-R9}

因为已知是满减,所以可以直接使用FD作为结尾也是没有问题的。但是要记住,虽然可以直接使用满减后缀,但是编译器也是会自动转换成第一种方法的。

得到:

3. 栈的应用举例

3.1 叶子函数的调用
	@ 初始化栈指针
    MOV SP, #0x40000020
	
MAIN:
		MOV R1, #3
		MOV R2, #5
		BL	FUNC
		ADD R3, R1, R2
		B stop
		
FUNC:
		@ 压栈保护现场
		STMFD SP!, {R1, R2}		@ 满减栈
		MOV R1, #10
		MOV R2, #20
		SUB R3, R2, R1
		@ 出栈恢复现场
		LDMFD SP!, {R1, R2}
		MOV PC, LR

结果:

        这段代码作用是:先初始化栈指针,0x40000020初始化地址,在FUNC函数中,首先使用STMFD指令将R1和R2的值压入栈中,以保护现场。以免等下重新赋值时覆盖了原有的值。然后计算R2-R1的值,并将结果存储在R3中。最后使用LDMFD指令将R1和R2的值从栈中弹出,以恢复现场

3.2 非叶子函数的调用
	@ 初始化栈指针
    MOV SP, #0x40000020
	
MAIN:
		MOV R1, #3
		MOV R2, #5
		BL	FUNC1
		ADD R3, R1, R2
		B stop
		
@ FUNC1:
		STMFD SP!, {R1, R2, LR}		@ 满减栈
		MOV R1, #10
		MOV R2, #20
		BL FUNC2
		SUB R3, R2, R1
		LDMFD SP!, {R1, R2, LR}
		MOV PC, LR
FUNC2:
		STMFD SP!, {R1, R2}	
		MOV R1, #7
		MOV R2, #8
		MUL R3, R1, R2
		LDMFD SP!, {R1, R2}
		MOV PC, LR

        非叶子函数,可能会再次覆盖之前入栈的数据,那么就要在入栈的程序后,在下一个程序中紧跟着入栈。

        这段ARM汇编代码演示了如何在函数调用期间使用栈来保护现场和恢复现场,并且演示了如何嵌套调用多个函数。该代码使用STMFD和LDMFD指令将当前函数的寄存器值保存到栈中,并在函数返回时将这些值从栈中弹出,以恢复现场,具体的动态结果可以自行演示!

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值