ARM②——汇编指令&寻址方式

ARM②——汇编指令&寻址方式

一、汇编指令概述

1、汇编指令一般格式:

alt

MOV	r0 , #10	//将立即数10 存入 r0寄存器
MOV r0 , r1		//将r1寄存器的值 存入到  r0寄存器

2、立即数

alt

  • 去除右边偶数个0,最后剩下的数小于等于8个2进制位,则为立即数。
  • Shift amount:移位的次数
  • Shift:移位方式

得出结论:一个数是否是立即数,取决于是否能移位(去除偶数个0),移位后的二进制数小于等于8位则为立即数

3、不是立即数的地址

​ 当我们要使用一些地址的时候,但这个地址不是立即数,这时候就无法将地址存入寄存器内,那么这种情况下,就需要用到伪指令的方法来实现。

ldr r0 , = 0xffffff

​ 这个方法在编译的时候,会把伪指令拆分成多条指令运行,从而达到目的。

二、汇编指令的使用

算数、逻辑运算指令

1、MOV 赋值(移动)

​ 搬移指令,将立即数或某个寄存器的值存入到目标寄存器

MOV	r0 , #3
MOV r1 , r2
MOV r3 , r4 , LSL#1

//搬移cpsr 和 spsr
cpsr  spsr
 mrs  r0, cpsr     
and  r0,#0x1
 msr  cpsr, r0     
//r0 = cpsr  把cpsr的值获取出来
//cpsr = r0

2、AND 与运算

AND{条件}{S}  <dest>, <op 1>, <op 2>
AND     R0, R0, #3; R0 = 保持 R0 的位 0 和 1,丢弃其余的位。

3、ADD 加法

ADD     R0, R1, R2              ; R0 = R1 + R2
ADD     R0, R1, #256            ; R0 = R1 + 256
ADD     R0, R2, R3,LSL#1        ; R0 = R2 + (R3 << 1)

4、ADC 带进位的加法

ADDS    R0, R4, R8              ; 加低端的字
ADCS    R1, R5, R9              ; 加下一个字,带进位
ADCS    R2, R6, R10             ; 加第三个字,带进位
ADCS    R3, R7, R11             ; 加高端的字,带进位

特殊情况:

​ 当做加法的时候,一个数大于32位,不能存入到寄存器内,那么这时候就可以把这个数,用两个寄存器来存储,(例如:r0存高位数值,r1存低位数值)

.global _start
_start:
	ldr r0	,	=0xffffffff
	mov r1	,	#1
	mov r2	,	#1
	mov r3	,	#0
	
	adds r4 , r0 , r2;	加低位的值 (如果溢出,会将进位标志自动置1)
	adcs r5 , r1 , r3;  带进位加法,加到高位值	(会自动取出进位标志位的状态,如果进位了,会变成r1 + r3 + 1(进位标志))
	

5、SUB & SBC

​ SUB 减法运算,SBC带借位的减法

SUB     R0, R1, R2              ; R0 = R1 - R2
SUB     R0, R1, #256            ; R0 = R1 - 256
SUB     R0, R2, R3,LSL#1        ; R0 = R2 - (R3 << 1)

​ SBC{条件}{S} , <op 1>, <op 2>

​ dest = op_1 - op_2 - !carry;

6、BIC 清除指定位的值(置0)

BIC     R0, R0, #%1011          ; 清除 R0 中的位 0、1、和 3。保持其余的不变。

BIC 真值表 :

  Op_1   Op_2   结果

  0      0      0
  0      1      0
  1      0      1
  1      1      0
译注:逻辑表达式为 Op_1 AND NOT Op_2

7、ORR 或运算

ORR     R0, R0, #3              ; 设置 R0 中位 0 和 1

OR 真值表(二者中存在 1 则结果为 1):

  Op_1   Op_2   结果

  0      0      0
  0      1      1
  1      0      1
  1      1      1

8、EOR 异或运算

相同为0 相异为1

EOR     R0, R0, #3              ; 反转 R0 中的位 0 和 1

EOR 真值表(二者不同则结果为 1):

  Op_1   Op_2   结果

  0      0      0
  0      1      1
  1      0      1
  1      1      0

9、MVN : 传送取反的值

MVN 从另一个寄存器、被移位的寄存器、或一个立即值装载一个值到目的寄存器。不同之处是在传送之前位被反转了,所以把一个被取反的值传送到一个寄存器中。这是逻辑非操作而不是算术操作,这个取反的值加 1 才是它的取负的值

  MVN     R0, #4                  ; R0 = -5

  MVN     R0, #0                  ; R0 = -1

移位运算

1、逻辑左移、逻辑右移

R0, LSR #1;		右移1位
R0, LSR R1;		右移R1里面存的数个位

R1, LSL #3;		左移3位

2、循环右移

tips:普通移位运算会丢失低位或者高位的数据,但是循环移位不会,比如右移:低位移出会自动补齐到高位。

R0, ROR #3;
R1, ROR R1;

乘法指令

MUL 乘法

MUL{条件}{S}  <dest>, <op 1>, <op 2>

                dest = op_1 * op_2

MLA 带累加的乘法

MLA{条件}{S}  <dest>, <op 1>, <op 2>, <op 3>

                dest = (op_1 * op_2) + op_3

比较、跳转指令

CMP 比较指令格式

CMP{条件}{P}  <op 1>, <op 2>
status = op_1 - op_2

B 跳转指令

b flag	@跳转到flag标识处

flag:

BL 可以返回的跳转指令(跳转时会将当前运行指令的地址存入LR寄存器,方便后续更改PC实现返回操作)

示例:比较R0 R1

cmp r0 , r1;
movgt r2 , r0;
movlt r2 , r1;
等价于:
if(r0 > r1)
{
	r2 = r0;
}
else
{
	r2 = r1;
}

举一反三:

if(r0 == 2 || r1 == 1)
{
	r2 = 1;
}
else
{
    r2 = 0;
}
//利用汇编写出分支语句

汇编实现:

.global _start
_start:
	cmp r0 , #2
	beq equal		@满足eq 等于条件 跳转到equal
	cmp r1 , #1
	beq equal		@满足eq 跳转
	bne notEqual	@不满足跳转到Not equal
equal:
	mov r2 , #1;
notEqual:
	mov r2 , #0;

函数示例

写一个函数 func 计算两个数的和(r0 , r1) 存到r2中,并且返回调用函数的地方

.global _start
_start:
	BL func		@BL跳转后  会将LR的值更新,LR = PC + 1 也就是第4行
	B end
func:
	ADD R2 , R0 , R1
	MOV PC , LR
end:
	nop
@虽然实现了基本的函数功能,但是没有实现实参和形参的区分,不够安全,还需要结合sp 出入栈来实现函数调用前后实参的安全性

关于栈的讲解(连续空间,栈专用)

alt

详细分为:

  • 空递增栈
  • 空递减栈
  • 满递增栈
  • 满递减栈(重点:Linux常用)

决定栈的类型:(14 和 58 可以互相替换使用的,常用5~8)

alt

  • STM 入栈
  • LDM 出栈

示例:

STMFD  R13!, {R0, R1}	@R13是LR寄存器,这里是满递减栈
LDMFD  R13!, {R1, R0}	@出栈顺序与入栈顺序相反(栈的特性)

非栈区空间(可用于数组等结构的实现)

  • STR——将Rx 写入[ ]空间内
  • LDR——从[ ] 空间取数值

示例讲解:

LDR R0 , [R1 , #4];		@等同于 r0 = *(r1 + 4); C语言的指针偏移量操作
STR R0 , [R1 , #4];		@等同于 *(r1 + 4) = r0;

三、寻址方式

1、立即数寻址

MOV R0 , #10		@R0 = 10 , 操作数2是立即数就是立即数寻址

2、寄存器寻址

MOV R1 , R2			@R1 = R2 , 操作数2为寄存器

3、寄存器移位寻址

MOV R1 , R2 ,LSL #4		@R2左移4位后给到R1

4、寄存器间接寻址

MOV R1 , [R2]		@将R2地址的数值取出来给R1

5、基址变址寻址

LDR R0 , [R1 , #4]		@R0 = *(R1 + 4)
LDR R0 , [R1 , #4]!		@R1 += 4 , R0 = *R1
LDR R0 , [R1] , #4		@R0 = *R1 , R1 += 4

6、多寄存器寻址

LDMxx R0 , [R1-R5]		@同时对R1到R5 5个寄存器操作 存储到R1-R5中(从R0地址开始)
STMxx R0 , [R1-R5]		

7、相对寻址

B flag		@跳转到flag  属于相对寻址

8、堆栈寻址

STMFD SP! , {LR , R0-R12}
LDMFD SP! , {R0-R12 , PC}

四、伪指令

.byte 单字节定义 .byte 0x12,’a’,23

.short 定义双字节数据 .short 0x1234,65535

.long /.word 定义4字节数据 .word 0x12345678

.quad 定义8字节 .quad 0x1234567812345678

.float 定义浮点数 .float 0f3.2

.string/.asciz/.ascii 定义字符串 .ascii “abcd\0”

number: .word 4

arr: .word 4,5,6,7,8

stack: .space 20

.text .text {subsection} 将定义符开始的代码编译到代码段

.data .data {subsection} 将定义符开始的代码编译到数据段,初始化数据段

.bss .bss {subsection} 将变量存放到.bss段,未初始化数据段

.global/ .globl :用来声明一个全局的符号

.end 文件结束 start 汇编程序的缺省入口是 start标号,用户也可以在连接脚本文件中用ENTRY标 志指明其它入口点

五、综合案例代码

.global _start		@将ARR4个数(数组),分别存入R0~R3寄存器
_start:
	LDR R4 , =arr
	LDMIA R4 , {R0-R3}

	NOP
	NOP
ARR:
	.word 3,4,5,6

汇编实现: 1~100的和

.global _start

_start:
 MOV R1 , #1     @1到100的累加和
 MOV R0 , #0

getSum:
 ADD R0 , R0 , R1    @存储器R0存储和 , R1控制循环变量
 ADD R1 , R1 , #1
 CMP R1 , #101
 blt getSum          @小于101跳转到getSum(循环)

 nop

gdb调试运行

alt

汇编实现:冒泡排序

.text
.global _start

_start:
    MOV R0 , #0         ;@R0控制外层循环
    MOV R10 , #4        ;
    LDR R2 , =ARR       ; 

outside: 
    CMP R0 , #4         ;@R0 == 4 跳出循环 (5-1次循环)
    BEQ END             ;@跳转到END 结束循环
    MOV R1 , #0         ;@给内层循环R0赋初值

inside:
    MUL R3 , R1 , R10   ;@数组偏移量 ARR[R2+R3]
    ADD R4 , R3 , #4    ;@ARR[R2+R3+1]

    CMP R1 , #3         ;@判断是否结束内层循环
    BGT out_count       ;@跳转到外层循环自增

    LDR R5 , [R2,R3]    ;
    LDR R6 , [R2,R4]    ;
    CMP R5 , R6         ;@比较R5和R6(ARR[i] > ARR[i+1])
    BGT swap            ;@跳转到交换
    B in_count          ;@不用交换直接跳转到自增

swap:                   
    STR R5 , [R2,R4]    ;
    STR R6 , [R2,R3]    ;

in_count:
    ADD R1 , R1 , #1    ;@内层循环自增1
    B inside		   ;

out_count:
    ADD R0 , R0 , #1    ;@外层循环变量自增1
    B outside           ;

ARR:
    .word 13,5,7,9,10   ;@数组

END:
    LDMIA R2 , {R3-R7}  ;
    NOP

gdb调试运行

alt

全部评论

相关推荐

刚刷到字节跳动官方发的消息,确实被这波阵仗吓了一跳。在大家还在纠结今年行情是不是又“寒冬”的时候,字节直接甩出了史上规模最大的转正实习计划——ByteIntern。咱们直接看几个最硬的数,别被花里胡哨的宣传词绕晕了。首先是“量大”。全球招7000多人是什么概念?这几乎是把很多中型互联网公司的总人数都给招进来了。最关键的是,这次的资源分配非常精准:研发岗给了4800多个Offer,占比直接超过六成。说白了,字节今年还是要死磕技术,尤其是产品和AI领域,这对于咱们写代码的同学来说,绝对是今年最厚的一块肥肉。其次是大家最关心的“转正率”。官方直接白纸黑字写了:整体转正率超过50%。这意味着只要你进去了,不划水、正常干,每两个人里就有一个能直接拿校招Offer。对于2027届(2026年9月到2027年8月毕业)的同学来说,这不仅是实习,这简直就是通往大厂的快捷通道。不过,我也得泼盆冷水。坑位多,不代表门槛低。字节的实习面试出了名的爱考算法和工程实操,尤其是今年重点倾斜AI方向,如果你简历里有和AI相关的项目,优势还是有的。而且,转正率50%也意味着剩下那50%的人是陪跑的,进去之后的考核压力肯定不小。一句话总结:&nbsp;27届的兄弟们,别犹豫了。今年字节这是铁了心要抢提前批的人才,现在投递就是占坑。与其等到明年秋招去千军万马挤独木桥,不如现在进去先占个工位,把转正名额攥在手里。
喵_coding:别逗了 50%转正率 仔细想想 就是转正与不转正
字节7000实习来了,你...
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务