ARM树莓派汇编代码无限循环问题求解及原理疑惑
我在ARM架构的Raspberry Pi上编写汇编代码,需求是遍历1到用户输入的数值,最终输出首个大于该输入值的数(比如输入5就输出6),这是个基础学习项目。但代码出现了无限循环问题——最初只在check_loop_range标签里加载变量值无法解决,后来在loop_range标签里也加载变量值后问题才解决,我搞不懂其中原理。
初始出错代码
check_loop_range: @ Checks if we should continue with loop_range ldr r0, =number @ r0 <- &number ldr r0, [r0] @ r0 <- *r0 ldr r1, =current_number @ r1 <- ¤t_number ldr r1, [r1] @ r1 <- *r1 cmp r0, r1 @ compare r0 and r1 blt end @ if r0 <= r1 branch to end loop_range: @ This loop goes trough all numbers from 1 to 'number' add r2, r1, #1 @ Add one to r1 ldr r1, =current_number @ r1 <- ¤t_number str r2, [r1] @ *r1 <- r2 b check_loop_range @ Continue loop
修正后代码
check_loop_range: @ Checks if we should continue with loop_range ldr r0, =number @ r0 <- &number ldr r0, [r0] @ r0 <- *r0 ldr r1, =current_number @ r1 <- ¤t_number ldr r1, [r1] @ r1 <- *r1 cmp r0, r1 @ compare r0 and r1 blt end @ if r0 <= r1 branch to end loop_range: @ This loop goes trough all numbers from 1 to 'number' ldr r0, =number @ r0 <- &number ldr r0, [r0] @ r0 <- *r0 ldr r1, =number @ 此处为笔误,应为current_number ldr r1, [r1] @ r1 <- *r1 add r2, r1, #1 @ Add one to r1 ldr r1, =current_number @ r1 <- ¤t_number str r2, [r1] @ *r1 <- r2 b check_loop_range @ Continue loop
完整工作代码
/* The data section hold the variables we will use */ .data .align 4 number: .word 0 @ Input give by the user .align 4 scan_pattern: .asciz "%d" @ Pattern to pass as argument to scanf .align 4 print_pattern: .asciz "%d\n" @ Pattern to pass as argument to printf .align 4 number_abundant: .word 0 @ The number of abundant numbers from 1 to 'number' .align 4 number_deficient: .word 0 @ The number of deficient numbers from 1 to 'number' .align 4 number_perfect: .word 0 @ The number of perfect numbers from 1 to 'number' .align 4 current_number: .word 1 @ We start evaluating numbers from 1, up to 'number' .align 4 return: .word 0 @ We will store lr here on function calls /* Code Section */ .text .global main main: ldr r1, =return @ r1 <- &return str lr, [r1] @ *r1 <- lr ; save return address ldr r0, =scan_pattern @ r0 <- &scan_pattern ; first argument to scanf ldr r1, =number @ r1 <- &number ; second argument to scanf bl scanf @ call scanf check_loop_range: @ Checks if we should continue with loop_range ldr r0, =number @ r0 <- &number ldr r0, [r0] @ r0 <- *r0 ldr r1, =current_number @ r1 <- ¤t_number ldr r1, [r1] @ r1 <- *r1 cmp r0, r1 @ compare r0 and r1 blt end @ if r0 <= r1 branch to end loop_range: @ This loop goes trough all numbers from 1 to 'number' ldr r0, =number @ r0 <- &number ldr r0, [r0] @ r0 <- *r0 ldr r1, =number @ 此处为笔误,应为current_number ldr r1, [r1] @ r1 <- *r1 add r2, r1, #1 @ Add one to r1 ldr r1, =current_number @ r1 <- ¤t_number str r2, [r1] @ *r1 <- r2 b check_loop_range @ Continue loop end: ldr r0, =print_pattern ldr r1, =current_number ldr r1, [r1] bl printf ldr lr, =return @ lr <- &return ldr lr, [lr] @ lr <- *lr bx lr @ exit /* External */ .global printf .global scanf
Makefile
all: Pro04 Pro04: Pro04.o gcc -o $@ $+ Pro04.o: Pro04.s as -g -mfpu=vfpv2 -o $@ $< clean: rm -vf Pro04 *.o
系统环境信息
Architecture: armv7l Byte Order: Little Endian CPU(s): 4 On-line CPU(s) list: 0-3 Thread(s) per core: 1 Core(s) per socket: 4 Socket(s): 1 Model name: ARMv7 Processor rev 4 (v7l) CPU max MHz: 1200.0000 CPU min MHz: 600.0000
PRETTY_NAME="Raspbian GNU/Linux 8 (jessie)" NAME="Raspbian GNU/Linux" VERSION_ID="8" VERSION="8 (jessie)" ID=raspbian ID_LIKE=debian HOME_URL="http://www.raspbian.org/" SUPPORT_URL="http://www.raspbian.org/RaspbianForums" BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
问题解析
初始代码无限循环的原因
退出条件的指令与注释不符:
注释写的是if r0 <= r1 branch to end,但实际用的blt指令是当第一个操作数小于第二个操作数时跳转(即r0 < r1时退出),而非小于等于。这意味着当current_number等于number时(r0 == r1),不会触发跳转,会继续进入循环。寄存器用途混淆导致的逻辑异常:
在loop_range中,你先使用r1存储current_number的数值做加法,随后又将r1赋值为current_number的内存地址用于存值。虽然跳回check_loop_range后会重新加载r1,但如果出现极端情况(比如number的数值刚好等于current_number的内存地址),会导致加法计算出错误值,无法正确更新current_number,最终陷入循环。
修正后代码的解决原理
你在loop_range中重新加载r0和r1的数值(注意此处有笔误,应将ldr r1, =number改为ldr r1, =current_number),本质是强制每次循环都从内存读取最新变量值,彻底避免了寄存器中残留的旧值(比如之前的内存地址)干扰计算逻辑。
另外,你修正后的笔误实际上让current_number直接被设为number+1,跳过了中间遍历步骤,所以看起来快速退出了循环,但这不是正确的遍历逻辑。更合理的修正应该是固定寄存器用途,比如用r1始终存current_number的地址,r2存数值,避免混淆:
check_loop_range: ldr r0, =number ldr r0, [r0] ldr r1, =current_number ldr r2, [r1] ; 用r2存current_number的数值 cmp r0, r2 blt end loop_range: add r2, r2, #1 ; 直接对数值做加法 str r2, [r1] ; r1始终是地址,无需重新加载 b check_loop_range
内容的提问来源于stack exchange,提问作者MiguelMahecha

