汇编循环遍历寄存器值的每一位

Posted

技术标签:

【中文标题】汇编循环遍历寄存器值的每一位【英文标题】:Assembly Loop Through Each Bit of Register Value 【发布时间】:2016-10-03 02:35:07 【问题描述】:

我有一个寄存器 $t0 ,其中存储了一些整数。例如,假设我将整数 1100 存储到它。这个值的二进制表示是 0000010001001100。当然,对于 32 位寄存器,它可以扩展到 32 位,但这很容易做到。

我正在尝试在汇编中实现一个循环,该循环遍历寄存器值的每一位并检查它是 1 还是 0。如何做到这一点?

也许我误解了寄存器的性质。据我了解,寄存器存储一个 32 位数字,是吗?这是否意味着每个位都存储在特定地址?

我曾尝试对寄存器使用移位并检查该位,但失败了。我还查看了 lb 命令,但这会加载字节,而不是位。那么人会怎么做呢?

【问题讨论】:

【参考方案1】:

一些基础知识:

大多数(全部?)移位指令将位移出进位标志

大多数(所有?)CPU 都有一个分支命令,用于跳转到设置进位标志的位置

结合这些,您可以执行以下操作:

load register1,< the value to be tested >
load register2, 0

Lrepeat:
    compare register1 with 0
    jump if zero Lexit

    shift right register1
    jump no carry Lskip
    increase register2 
Lskip:
    jump Lrepeat

Lexit:                      ; when you end up here, your register2
                            ; holds the count of bit register1 had set

仍然可以进行一些优化:

Lrepeat:
    compare register1 with 0
    jump if zero Lexit
    shift right register1
    jump no carry Lskip       <-- note: here you jump ...
    increase register2 
Lskip:
    jump Lrepeat              <-- ... to jump again!
Lexit:

=====>

Lrepeat:
    compare register1 with 0
    jump if zero Lexit
    shift right register1
    jump no carry Lrepeat     <-- so you can optimize it this way
    increase register2 
Lskip:
    jump Lrepeat             
Lexit:

有些 CPU 有“加进位”指令, 例如6502:

ADC register,value      ; register = register + value + Carry flag

这可以用来避免分支(条件跳转),通过在每个循环的 register2 中添加 0(当然还有进位)

    shift right register1
    add with carry register2,0   ; may look weird, but this adds a 
                                 ; 1 if the carry is set, which is
                                 ; exactly what we want here
    jump Lrepeat

请注意,您不需要知道寄存器大小!您只需循环直到寄存器为 1,这可以为您节省大量时间,例如如果您的值类似于 0000 0000 0000 0000 0000 0000 0001 1001

【讨论】:

我正在使用 MIPS。这个好像没有进位标志?对?或者我错过了什么。此外,在循环 Lrepeat 下,您说“将寄存器 1 与 0 进行比较”。如果寄存器 1 有一些值(比如 1450,当然转换为二进制),那么寄存器与 0 相比是什么值?我认为这样做的目的是找到 1,但那将如何工作? 哦,我想你混合了一些东西,我还不够清楚。 "jump if zero" MIPS 没有标志。它有像BEQ reg1, reg2, targetBGEZ reg, target 这样的指令来比较大于零和分支。 mrc.uidaho.edu/mrc/people/jff/digital/MIPSir.html。所以我猜你会andi $t0, $t1, 0x1 设置$t0 = $t1 的低位,然后你可以分支。 (有一个架构零寄存器,所以你可以beq $t0, $0, low_bit_zero【参考方案2】:

在任何处理器上,设置一个循环来计算寄存器中的位数,在本例中为 32。每次通过循环时,将感兴趣的寄存器与 1 相加。然后将结果添加到累加器中,然后最后转移寄存器。这给了你设置的位数。

确切的指令因处理器而异。要循环,您通常会设置一个标签,递减一个计数器,然后执行一条名为 branch_not_equal_to 零的指令(BNZ、BNEQ0 类似)。 and 的名称类似于 AND、ANDI(和立即数)。 ADD 可能是 ADD、ADC(带进位相加)。移位类似于 ASR(算术右移)LSR(逻辑右移),您可能必须将其传递 1 才能表示仅移动一个位置。但是所有处理器都允许您以这种方式读取寄存器位。

【讨论】:

@Malcom McLean AND 有什么作用?编辑:我已经编辑了这个问题。我的意思是问什么并且没有添加我道歉 将两个数字相加。通常是两个寄存器。 @Malcom McLean 我已经编辑了我的评论,请看一下 AND 意味着 0 0 = 1, 1,0 = 0, 0,1 = 0, 1,1 = 1。所以如果 a 和 b 都是 1,你只会得到 1。你这样做寄存器中每个位的操作。一种重要的用途是屏蔽位。在这里,我们与 1 只得到最后一位。 r1 = r2 AND 1 如果 r2 的最后一位清零,则将 r1 设置为 0,如果设置了,则设置为 1。所有其他位必须为零。 有没有推荐的教程/链接来更好地理解汇编?我理解 AND(我理解使用 AND 来检查每一位,但不是你所说的 AND 只得到最后一位......)的作用,但正如你提到的,我在它的应用程序中迷失了方向。我相信这更多是由于我对组装的新手理解而不是您的解释。尽管如此,您的帮助仍然受到赞赏。谢谢【参考方案3】:

将要迭代的寄存器表示为 R0。因此,例如,最低有效位是R0= 1011 0101

2) 使用第二个清除寄存器R1=0000 0001

3) AND R1R0 然后右移 R0(所以下一次迭代检查 R0 的下一位)。

4) 将R3 设为以 1 递增的第三个寄存器 IF AND 运算结果为 1(即,您在 R0 中遇到 1)。 ELSE,再次循环检查R0中的下一位。

您可以通过递减循环计数器来遍历整个 32 位或您选择的长度。

【讨论】:

以上是关于汇编循环遍历寄存器值的每一位的主要内容,如果未能解决你的问题,请参考以下文章

汇编11:标志寄存器

汇编语言——标志寄存器

汇编语言程序设计中,段寄存器的内容和偏移量合起来是内存地址,

Atmega32 移位 PORTA 不会循环遍历整个寄存器

STM32中的BRR寄存器与BSRR寄存器重复么?

汇编_标志寄存器flag_串数据的传送