MASM汇编:将8位寄存器移动到16位寄存器中(例如,mov cx,ch)

5
我决定学习汇编语言编程。我正在使用这个8086教程。在底部的练习中,需要找出一些指令中的错误,其中之一是:
mov cx, ch 

我在这个主题上在SO上发现了一些类似的问题,解释了如何实现它,但现在我想知道为什么禁止此操作?

假设我有10d = 00001010b在CH中,想将其放入CL并同时擦除CH。 mov cx,ch似乎可以做到这一点,因为它将10d显示为16位00000000 00001010,并分别放入CH和CL(整个CX)。

哪里出了错,为什么给定的教程要求在这个表达式中找到错误?


2
它并不是被禁止,更多的是“在所有英特尔操作码的完整列表中没有提供”(PDF链接)。所以,是的,因为操作码不存在,所以它是被禁止的。 - Jongware
1
我非常怀疑你困惑的主要原因是作者(可能是故意的)选择了CHCLCX寄存器来解决这个问题。为了帮助你理解这种不连续性,你可以使用mov dx, ch得到一个非常相似的错误,这可能会更好地阐明错误的原因。这只是一个建议;如果不满意可以退款。 - User.1
相关内容:MOV 8位到16位寄存器(al到bx)本质上是一个重复的问题。 - Peter Cordes
4个回答

8

mov指令用于在相同大小的操作数之间移动。你想要的是将8位的ch扩展为16位的cx。有两个可用于此目的的指令:

movzx cx,ch  ; zero-extends ch into cx. the upper byte of cx will be filled with zeroes
movsx cx,ch  ; sign-extends ch into cx. the upper byte of cx will be filled with the most significant bit of ch

在这种情况下,实现相同效果的另一种方法是:
shr cx,8  ; zero-extend
sar cx,8  ; sign-extend

1
零扩展可以通过 mov cl,ch; xor ch,ch 实现。由于在 8086 上需要使用 cl,因此无法通过 sar cx,8 获得符号扩展,因为移位超过一个位置。解决方案是 mov cl,8; sar cx,cl,但这在 8088 和 8086 处理器上速度较慢。 - chqrlie
@chqrlie:这被标记为[tag:masm32]。这至少意味着一个后8086开发环境,而问题中没有任何暗示要开发向后兼容8086的代码。这在今天大多数情况下都不太相关,除非针对嵌入式8086微控制器(或使用emu8086做作业,但那里性能并不重要)。 - Peter Cordes
@PeterCordes:实际上,这个问题被标记为“masm”,OP提到了这个页面:https://web.archive.org/web/20150318063331/http://www.csi.ucd.ie/staff/jcarthy/home/alp/alp1.html * 8086编程简介*...在32位和64位汇编中仍然可以调整8位和16位寄存器,但更近期的教程关注于更有趣的内容。 - chqrlie
@chqrlie:在我重新标记之前,我说“是”(https://stackoverflow.com/posts/29716796/revisions),包括x86和零扩展(基于OP提到的位模式),因为答案不特定于masm32,更不用说masm了。但好吧,我没有查看问题正文中的链接。 - Peter Cordes

2
问题在于,您试图将8位寄存器ch的内容移动到16位寄存器cx中。由于寄存器大小不同,这是不可能的。
因此,我猜您会收到类似于“无效的操作码和操作数组合”的错误消息。
强调一下,上面的8和16已经交换了位置,但陈述保持不变。您可以查看此概述,就会发现没有定义不同寄存器大小的组合。这意味着不存在代表mov cx, ch的OPcode。

将[源]中的内容移动到[目标]中。我尝试将8位移动到16位。 - Bartłomiej Szałach
1
抱歉,我更正了那个错误。但是这个说法基本上仍然有效:在mov命令中不能混合使用16位寄存器和8位寄存器。 - Trinimon

2

您想将8086中CH的内容移动到CX中。

在更近期的处理器上,例如80286,您可以通过向右移8个位置来移动CX的值,可以选择带符号复制或不带符号复制:

; zero extend ch into cx
    shr cx,8

; sign extend ch into cx
    sar cx,8

这些指令在8088或8086上不可用。您必须使用CL来指定移位计数:

; zero extend ch into cx
    mov cl,8
    shr cx,cl

; sign extend ch into cx
    mov cl,8
    sar cx,cl

然而,这种方法非常缓慢,因为每个位置的变量移位需要多个周期。

以下是更快的方法:

; zero extend ch into cx
    mov cl,ch
    xor ch,ch

; sign extend ch into cx
    mov cl,ch
    neg ch     ; set the carry flag if ch is negative
    sbb ch,ch  ; set all bits if ch was negative, clear them otherwise

如果您可以销毁AX,那么您可以使用专为此设计的cbw来节省代码大小。在原始的8086和特别是8088上,小=快,因为代码获取是主要瓶颈。但在现代x86上并非如此。

; sign extend ch into ax
    mov   al, ch
    cbw                 ; sign-extend AL into AX
; optionally move back to cx
    xchg  cx, ax        ; smaller than mov cx, ax

为了避免破坏AX,您可以执行mov cl,chxchg ax,cxcbw并停止,或者进行最后的xchg ax,cx以将CH符号扩展为CX并恢复其他所有内容。xchg与AX是一个1字节指令,cbwcwd(例如在16位idiv之前将AX扩展为DX:AX)也是如此。 cbw与386 movsx ax,al完全相同。

1
在8086上,当代码大小是性能的主要因素时,可以使用更好的方法:mov cl, chxchg ax, cxcbwxchg ax, cx。这种情况(AX由于缺少movsx、2操作数imul等而特殊),是为什么8086花费8个操作码来具有xchg ax,reg的单字节短格式的原因。 - Peter Cordes
1
或者如果你可以销毁 AX,mov al,chcbwxchg ax,cx - Peter Cordes
@PeterCordes:是的,确实如此!看起来我们来自同一个游乐场 :) - chqrlie
1
有疑虑;我从未在真正的8086上运行过我编写的任何东西,甚至从未以实模式运行过。我只是碰巧知道如何针对x86进行代码大小优化,这是为了好玩和作为性能的决胜因素。但像这样的东西,主要是为了大小而非速度,大多数用于Tips for golfing in x86/x64 machine code,以及理解历史ISA设计决策对当前x86的负担。但如果你的意思是我们喜欢为了好玩和利润而进行优化,那么是的 :) - Peter Cordes

0

只需简单的指示,就可以做到。

mov cl,ch  ; copy high bits to low
xor ch,ch  ; clear high-bits

在16位编程中,这是很常见的,而且只需要2个时钟周期

使用movezx/movsx需要3个时钟周期。使用

movsx cx,ch

用带有 s符号扩展的方式将字节移动到字中

movzx cx,ch

使用零扩展将字节移动到字


在大多数现代x86 CPU上,movzx/movsx具有1个周期的延迟,并且是单个uop。就吞吐量而言,这意味着它们在典型的4-wide OoO exec CPU上需要0.25个周期。但是,在Haswell/Skylake上从CH读取会增加一个额外的延迟周期。没有指定微架构,谈论周期成本是没有意义的。(并且在乱序CPU上,“周期成本”不是一件简单的一维成本之和,性能也不是如此) - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接