bc汇编指令用法_第六章 条件指令

5c418dd40664e317e9252f86d65dd584.png

条件指令

在上一章中,我们学习了分支指令。 它们确实是功能强大的工具,因为它们使我们能够表达控制结构。 结构化编程是更好的计算工程学中的一个重要里程碑(虽然是基础的,但仍然是重要的)。 因此,能够在我们的处理器中的汇编器中映射常用的结构化程序结构是一件好事。

If, then, else

好吧,这是一个基本的结构,实际上,我们在上一章中已经使用了这种结构。 考虑以下结构,其中E是表达式,而S1和S2是语句(它们可以是{SA; SB; SC;}之类的复合语句)

if (E) then

S1

else

S2

在ARM汇编器中表达这种情况的一种可能方法如下

if_eval:

/* Assembler that evaluates E and updates the cpsr accordingly */

bXX else /* Here XX is the appropiate condition */

then_part:

/* assembler for S1, the "then" part */

b end_of_if

else:

/* assembler for S2, the "else" part */

end_of_if:

如果没有其他部分,我们可以将bXX else替换为bXX end_of_if。

Loops

这是结构化编程中的另一个常见用法。 尽管有几种类型的循环,但实际上所有循环都简化为以下结构。

while (E)

S

假设S做某事,所以E最终变为假,并留下了循环。 否则,我们将永远停留在循环中(有时这是您想要的,但在我们的示例中不是)。 实现这些循环的方法如下。

while_condition : /* assembler to evaluate E and update cpsr */

bXX end_of_loop /* If E is false, then leave the loop right now */

/* assembler of S */

b while_condition /* Unconditional branch to the beginning */

end_of_loop:

常见循环涉及从单个整数范围进行迭代,例如

for (i = L; i < N; i += K)

S

也可以写成

i = L;

while (i < N)

{

S;

i += K;

}

1 + 2 + 3 + 4 + … + 22

作为第一个示例,让我们将1到22之间的所有数字相加(我稍后会告诉您为什么选择22)。 总和的结果是253(用计算器检查)。 我知道计算已经知道的结果几乎没有意义,但这只是一个例子。

890bb6406cf606d9c81d1c10fa06e25b.png

在这里,我们从1到22进行计数。我们将使用寄存器r2作为计数器。 如您在第6行中看到的,我们将其初始化为1。总和将累积在寄存器r1中,在程序结束时,我们将r1的内容移至r0,以将总和的结果作为错误代码返回给r0。 程序(我们本可以在所有代码中使用r0并避免最后的移动,但我认为这种方式更清楚)。

在第8行中,我们将r2(请记住,计数器从1到22)与22进行比较。这将更新cpsr,因此在第9行中,我们可以检查比较结果是否使r2大于22。 在这种情况下,我们通过分支到结束来结束循环。 否则,我们将r2的当前值与r1的当前值相加(请记住,在r1中,我们将1到22的总和累加)。

11行很重要。 我们增加r2的值,因为我们从1计数到22,并且已经将r2中的当前计数器值加到r1中的总和结果中。 然后在第12行,我们在循环的开头分支回到分支。 请注意,如果第11行不存在,我们将挂起,因为第8行的比较将始终为false,并且我们永远不会离开第9行的循环!

$ ./loop01; echo $?

253

好吧,现在您可以更改第8行,并尝试使用#100。 结果应为5050。

$ ./loop01; echo $?

186

发生了什么? 好吧,碰巧在Linux中,程序的错误代码是从0到255(8位)的数字。 如果结果为5050,则仅使用数字的低8位。 二进制中的5050是1001110111010,其低8位是10111010,恰好是186。如何在结束程序之前检查计算的r1是5050? 让我们使用GDB。

$ gdb loop

...

(gdb) start

Temporary breakpoint 1 at 0x8390

Starting program: /home/roger/asm/chapter06/loop01

Temporary breakpoint 1, 0x00008390 in main ()

(gdb) disas main,+(9*4)

Dump of assembler code from 0x8390 to 0x83b4:

0x00008390 :movr1, #0

0x00008394 :movr2, #1

0x00008398 :cmpr2, #100; 0x64

0x0000839c :bgt0x83ac

0x000083a0 :addr1, r1, r2

0x000083a4 :addr2, r2, #1

0x000083a8 :b0x8398

0x000083ac :movr0, r1

0x000083b0 :bxlr

End of assembler dump.

让我们告诉gdb在执行mov r0,r1之前在0x000083ac处停止。

(gdb) break *0x000083ac

(gdb) cont

Continuing.

Breakpoint 2, 0x000083ac in end ()

(gdb) disas

Dump of assembler code for function end:

=> 0x000083ac :movr0, r1

0x000083b0 :bxlr

End of assembler dump.

(gdb) info register r1

r1 0x13ba5050

太好了,这是我们所期望的,但由于错误代码的限制,我们无法看到。

也许您已经注意到,将我们的标签识别为功能时会发生一些奇怪的事情。 我们将在以后的章节中解决这个问题,尽管这基本上是无害的。

3n + 1

让我们再举一个例子稍微复杂一点。 这是著名的3n +1问题,也称为Collatz猜想。 给定一个数字n,如果它是偶数,我们将其除以2,然后将其乘以3,如果它是奇数,则将其加一。

if (n % 2 == 0)

n = n / 2;

else

n = 3*n + 1;

在继续之前,我们的ARM处理器能够将两个数相乘,但是我们应该学习一条新的指令mul,这会使我们稍微走弯路。 相反,我们将使用以下标识3 * n = 2 * n + n。 我们还真的不知道如何乘以或除以2,我们将在以后的章节中对此进行研究,因此现在仅假设其工作原理如下面的汇编器所示。

Collatz猜想指出,对于任何数量的n,重复应用此过程最终将得出的数字为1。从理论上讲,事实并非如此。 到目前为止,还没有找到这样的数字,但是没有另外证明。 如果我们要重复应用前面的过程,则我们的程序正在执行类似的操作。

n = ...;

while (n != 1)

{

if (n % 2 == 0)

n = n / 2;

else

n = 3*n + 1;

}

如果Collatz猜想是错误的,则将存在上面的代码将挂起的n个,永远不会达到1。但是正如我所说,没有找到这样的数字。

/* -- collatz.s */

.text

.global main

main:

mov r1, #123 /* r1 ← 123 */

mov r2, #0 /* r2 ← 0 */

loop:

cmp r1, #1 /* compare r1 and 1 */

beq end /* branch to end if r1 == 1 */

and r3, r1, #1 /* r3 ← r1 & 1 */

cmp r3, #0 /* compare r3 and 0 */

bne odd /* branch to odd if r3 != 0 */

even:

mov r1, r1, ASR #1 /* r1 ← (r1 >> 1) */

b end_loop

odd:

add r1, r1, r1, LSL #1 /* r1 ← r1 + (r1 << 1) */

add r1, r1, #1 /* r1 ← r1 + 1 */

end_loop:

add r2, r2, #1 /* r2 ← r2 + 1 */

b loop /* branch to loop */

end:

mov r0, r2

bx lr

4e4ccf7d3e642562a2e39b559b9ed0d9.png

在r1中,我们将保留数字n。 在这种情况下,我们将使用数字123。123以46个步骤达到1:[123、370、185、556、278、139、418、209、628、314、157、472、236、118、59、178, 89、268、134、67、202、101、304、152、76、38、19、58、29、88、44、22、11、34、17、52、26、13、40、20、10, 5、16、8、4、2、1]。 我们将计算寄存器r2中的步数。 因此,我们将r1初始化为123,将r2初始化为0(尚未执行任何步骤)。

在循环的开始,在第8行和第9行,我们检查r1是否为1。因此,将其与1进行比较,如果相等,则使循环分支结束。

现在我们知道r1不是1,因此我们继续检查它是偶数还是奇数。 为此,我们使用一条新指令,该指令执行按位与运算。 偶数的最低有效位(LSB)为0,而奇数的LSB则为1。因此按位使用1将分别对偶数或奇数返回0或1。 在第11行中,我们将按位运算的结果保存在r3寄存器中,然后在第12行中,将其与0进行比较。如果不为零,则分支为奇数,否则继续偶数情况。

现在,第15行发生了一些没出现过的写法,这是ARM允许我们执行的组合操作。 这是一个动作,但我们不会将r1的值直接移到r1(这将不执行任何操作),但是首先我们对r1的值(取值,而不是寄存器本身)进行算术右移(ASR)。 然后,该移位值被移至寄存器r1。 算术右移会将寄存器的所有位向右移:有效地舍弃了最右边的位,最左边的位设置为与移位前最左边的位相同的值。 向右移动一位到数字与将该数字除以2相同。因此,此mov r1,r1,ASR#1实际上正在执行r1←r1 / 2。

第18行的偶数情况也发生了类似的写法,在这种情况下,我们正在做加法。 第一个和第二个操作数必须是寄存器(目标操作数和第一个源操作数)。 第三个结合了逻辑左移(LSL)。 操作数的值向左移动1位:最左边的位被舍弃,最右边的位设置为0。这实际上是将值乘以2。因此我们将r1(保持n的值)加到2 * r1。 这是3 * r1,所以3 * n。 我们再次将此值保留在r1中。 在第19行中,我们向该值加1,因此r1结束时具有我们想要的值3 * n + 1。

现在不必担心这些LSL和ASR。 现在就把它们视为理所当然。 在以后的章节中,我们将更详细地介绍它们。

最后,在循环的末尾,在第22行,我们更新r2(请记住,它保留了我们的步骤的计数器),然后分支回到循环的开始。 在结束程序之前,我们将计数器移至r0,以便返回达到1的步数。

$ ./collatz; echo $?

46

好了,今天就这些。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值