从最高有效位或高位开始提取寄存器的位
Posted
技术标签:
【中文标题】从最高有效位或高位开始提取寄存器的位【英文标题】:Extract bits of a register starting with the most significant bit, or a high bit 【发布时间】:2019-04-02 04:55:07 【问题描述】:编辑:
我没想到这个问题会如此迅速地引起关注。根据我已经收到的答案,我似乎遗漏了一条重要信息。该模式不是固定数量的位。有些字母可能有更多或更少的位。即,B 有 5 位,但 C 可能使用多达 6 位,但没有使用超过一个字节。我在我的问题中包含了一个“A”位模式的示例,每行使用 7 位。另请参阅问题底部的编辑。
我是组装新手。我有对应于字母的文本表示的位模式。每个 1 代表一个 $(或任何符号),每个 0 代表一个空格。即:
$$$$ 11110
$ $ 10001
$ $ 10001
$$$$ 11110
$ $ 10001
$ $ 10001
$$$$ 11110
$ 0001000
$ $ 0010100
$$$$$ 0111110
$ $ 1000001
我编写了一个汇编语言程序,它读取每个模式并根据它读取的是 1 还是 0 打印正确的符号。要确定它是 1 还是 0,我将寄存器与 1 然后将位右移等于每行中位数的次数,然后比较结果:
请注意,每行的位存储在单独的 2 字节字的底部,我将其加载到 8 位寄存器中。
patternb: dw 011110b,010001b,010001b,011110b,010001b,010001b,011110b
rowloop:
mov bl,[patternb+si] ;iterate through each element in binary array
patternloop:
mov bh,bl ;move bit pattern into register so that we can change it
and bh,1 ;AND register to find set bits and store back in register
shr bl,1 ;SHIFT original bit pettern right
cmp bh,1 ;check if bit is set or not (1=set, else 0)
je writesym ;if set, write symbol
jne writeblank ;if not set, write space
问题在于 AND 的工作方式。显然,它从最低有效位开始并在位右移时打印,但这会导致它以“反向”顺序打印字母的问题。即:
####
# #
# #
####
# #
# #
####
我尝试了一些操作,但似乎都没有。我还尝试移动和旋转位模式以对应正确的打印,但这不适用于每一行,因为并非每一行都需要以这种方式进行操作。 (例如,第 2 行将正确打印,无需先进行操作)。对于 A-E 中的每个字母,我都有相同的位模式技术。
理想情况下,我希望它以某种方式从最高有效位开始比较,然后以正确的顺序打印它,但我不确定如何处理为了实现这一目标。
编辑:
按照 Peter Duniho 的回答,我想发布一些我尝试过的东西:
我尝试使用 10000b 对模式进行与运算,然后对结果进行 ROL 以获得00001b
的答案,然后将位左移。然后比较结果以查看应该打印哪个符号。这也不起作用,但因为位模式并不总是固定的,所以无论如何它都不是解决方案。
mov bh,bl ;move bit pattern into register so that we can change it
and bh,10000b ;AND register to find set bits and store back in register
rol bh,1 ;rol result to obtain 00001b
shl bl,1 ;SHIFT original bit pettern right
cmp bh,1 ;check if bit is set or not (1=set, else 0)
je writesym ;if set, write symbol
jne writeblank ;if not set, write space
我现在最接近解决这个问题(在 Peter Duniho 的回答作为指导的帮助下)是将我的位数组存储为完整的 8 位形式(即 011110000b
等而不是 011110b
否则汇编器将其存储为 00011101
隐式存储,如 Martin Rosenau 的回答中所述,我们不想要)以及完整的 10000000b
(因为我们最多使用 8 位,这让我们可以检查 MSB) 1 个(000000001b
),就像我之前尝试做的那样,然后使用上面的 ROL 和比较方法(或者只是将它与10000000b
进行比较)。循环总共运行了 7 次(由于每个字母有 7 行/位模式,除了 A 有 4 所以 A 打印不正确,但这是我可以在某些条件下自己解决的问题。程序工作并打印现在正确。这是我使用的代码:
mov bh,bl ;move bit pattern into register so that we can change it
and bh,10000000b ;AND register to find set bits and store back in register
rol bh,1 ;shift MSB to LSB to compare (or could just compare with 10000000b instead)
shl bl,1 ;SHIFT original bit pettern left
cmp bh,1 ;check if bit is set or not (or use cmp bh,10000000b and omit rol above)
je writesym ;if set, write symbol
jne writeblank ;if not set, write space
我已将 Peter 的解决方案标记为答案,因为它为我指明了解决此问题的正确方向。但正如他所提到的,有很多方法可以解决这个问题(如发布的不同解决方案所示),但他恰好是我为我自己的代码实现的最简单的方法,这正是他想要的。
Martin Rosenau 的回答也很有见地,尤其是优化。当我有更多时间时,我会尝试实现这些,然后更新上面的解决方案。
【问题讨论】:
左移一点,从顶部移到 CF 中。例如add bl,bl / jc writesym
/ 否则失败或jmp
写空白。
re:编辑:如果它实际上是可变宽度,您的代码如何知道位模式的开始位置?即使前几列全为零,将每个块视为始终为 8x7 似乎要容易得多。然后你可以按照我在第一条评论中建议的方式简单地解码它,一次将一位移动到 CF 中。
【参考方案1】:
理想情况下,我希望它以某种方式从最重要的位开始比较,然后以正确的顺序打印它
对我来说似乎是个好主意。你有没有尝试过这些方面的任何事情?如果是这样,您具体尝试了什么?您遇到了什么具体困难?
与此同时……
什么决定了要检查的位位置数(即循环计数)?是固定的吗?如果是这样,为什么不只是将位与高端而不是低端(例如10000b
,又名16
)并左移而不是右移?
例如
mov bh,bl ;move bit pattern into register so that we can change it
and bh,10000b ;AND register to find set bits and store back in register
shl bl,1 ;SHIFT original bit pattern left
cmp bh,10000b ;check if bit is set or not (1=set, else 0)
je writesym ;if set, write symbol
jne writeblank ;if not set, write space
如果您直到运行时才知道循环计数,您可以为每次迭代移动:
mov bh,bl ;move bit pattern into register so that we can change it
shr bh,cl ;the assumption being that cl has the width of your bit pattern
dec cl ;next bit
and bh,1 ;AND register to find set bits and store back in register
cmp bh,1 ;check if bit is set or not (1=set, else 0)
je writesym ;if set, write symbol
jne writeblank ;if not set, write space
如果您已经在循环中使用 CX,显然上述内容需要稍作修改。但希望您能了解基本概念。
上面的一个变体将是例如通过存储1
并左移适当的计数(例如shl al,cl
)将AND位模式存储在另一个寄存器中(例如al
),然后使用al
作为操作数而不是第一个中的10000b
上面的例子。
这些远非您唯一的选择。您需要大大缩小问题的限制以获得更具体的答案。但是,假设这是一个学习 ASM 的练习,那么这是您阅读和了解更多关于您可用的位操作操作的好机会。 :)
【讨论】:
您应该简化 OP 的代码,例如shl bl,1
/ test bl, 1<<5 / jnz writesym
。它不需要 2 个寄存器或任何复制,也绝对不需要可变计数移位。 (如果您有 386 条指令,您可以使用 bt bx, cx
处理变量计数,以设置 CF = CX 选择的位位置。否则,您可以在循环外为 test
创建掩码。)
@PeterCordes:是的,感谢您提供的额外积分。不幸的是,这个主题有很多变化。我希望不必编写所有变体的详尽枚举,而是发布一些非常接近 OP 已经在做的事情,以尽量减少潜在的混淆(即在这里,教学问题优先于代码效率,恕我直言)。但你是对的......还有其他更好的方法来执行这些操作。
我会为每种方式写一个有效的例子,就像马丁的回答一样。无需列举所有方法,甚至无需尝试,但仅使用几条指令就可以让未来的读者更容易理解该示例,并且是一个更好的复制示例。我认为能够遵循程序逻辑的初学者一旦看到以简单的方式完成后,就可以很快看出他们的版本是如何过于复杂的。所以我不认为消除低效率是理解的障碍。此外,这只是一个读者;其他人可能并没有陷入他们的复制和销毁策略。
Stack Overflow 的答案在各个方面都应该是很好的例子,未来的读者可以从中学习,或者复制/粘贴而不用自找麻烦。我强烈不同意将 OP 的低效做事方式最小化的做法,当它很容易并且高效的版本更短更简单且更少进行时。
@PeterCordes:欢迎您提出意见。 :) 告诉你什么:你发布的答案提供了很好的细节水平,并演示了并解释你所说的优化应该在答案中,我将删除我的答案并向上-投票给你。这似乎比评论我的答案更有效地利用了你的时间。【参考方案2】:
问题在于 AND 的工作方式。
您的第一个问题是右移的工作方式。右移将删除“图像”的最右侧“像素”并将该像素左侧的像素移动到最右侧的位置(这样该像素将成为下一个要打印的像素):
"$$$ $ $ " ->
" $$$ $ $" ->
" $$$ $ " ->
" $$$ $" ->
...
如果要从左到右打印像素,则必须执行左移,而不是右移。
(请注意,可以使用shl bl,1
或add bl,bl
完成左移。)
因为您的“图像”只有 5 个“像素”宽,但一个字节有 8 位,所以您必须决定是在图像的左侧还是右侧添加未使用的位。
例如:
"$$$ $" = 11101000 or 00011101 ?
假设您决定在左侧添加像素(00011101 - 如果您将数字指定为 011101b
,汇编程序将隐式执行此操作)。
然后您必须执行AND
操作,其值具有代表最左边像素集的位:
Old: New:
and bh,1 and bh, 010000b
shr bl,1 shl bl, 1
顺便说一句:您的程序有两种可能的优化:
1) 利用最左边的位不丢失的事实:
patternloop:
shl bl,1
test bl,0100000b
je writesym
这种优化利用了字节中有 3 个空闲位的事实,因此字节的左位不会在左移中“丢失”:
"0 0 0<1>1 1 0 1"
-> Left shift ->
"0 0<1>1 1 0 1 0"
"< >" = Bit you are interested in
指令test bl,xxx
影响ZF
标志(影响je
指令)的方式与组合两个指令and bl,xxx
后跟cmp bl,0
的方式相同,但它不会修改bl
注册!
2) 利用右移将位移出到CF
这一事实:
patternloop:
shl bl,1
jc writesym
此优化假设“$$$ $”存储为 11101000 而不是 00011101。它使用 shl
将在执行移位之前将最左边(最高)位复制到 CF
标志的事实(假设移位 1 位):
BL="<1>1 1 0 1 0 0 0", CF=?
-> Left shift (using SHL or ADD) ->
BL="1 1 0 1 0 0 0 0", CF=<1>
如果设置了CF
标志,jc
指令将执行跳转。
【讨论】:
即将使用add
/ test
发布类似的内容。刚刚想到shl bl, 3
before 循环会将感兴趣的位带到顶部并设置add bl,bl
/ jc
循环,保存test bl, 1<<5
。
shl 将在进行移位之前将最左边(最高)位复制到 CF 标志:这仅适用于移位 1,并且可能会混淆 shl bl, 2
。我认为英特尔描述它的方式更容易理解,因为 CF 将最后一位移出。 (或者当然,使用add bl,bl
使其成为普通添加的实际进位,并且更有效,因为它可以在现代 CPU 上与jc
进行宏融合。)
@MartinRosenau:我认为他的意思是你写 010000h
的那一行(十六进制,而不是二进制)。但由于 NASM 支持表达式,IMO 更容易编写 test bl, 1<<5
在源中写入位位置,而不是让读者计数。或 1<<(5+1)
轮班后。
@PeterDuniho 是的。这应该是“b”而不是“h”。我更正了。【参考方案3】:
您可以使用操作码 ROL
将位向 MSB 旋转 1,然后 MSB 将旋转到 LSB 的位置,例如:
11110000 -> ROL 1 -> 11100001
这样你就可以做类似的事情:
ROL1 -> 测试 LSB -> ROL1 -> 测试 LSB -> ....
在您的情况下,bl 是一个 8 位注册器,循环 ROL,测试 8 次以绘制 ascii 艺术
【讨论】:
这不适用于给定的样本数据,即只有 5 个有效位。 您正在尝试帮助那些明显缺乏位操作经验的人,如果不是一般的组装。您有必要确保您的答案没有省略诸如“忽略前三个 ROL”之类的细节。对于那些不会自然而然地自行推断出这一切的人来说,这非常重要。以上是关于从最高有效位或高位开始提取寄存器的位的主要内容,如果未能解决你的问题,请参考以下文章