Assembly 哪个2';如果只需要结果的低位,则可以使用s补码整数运算,而无需将输入中的高位置零?

Assembly 哪个2';如果只需要结果的低位,则可以使用s补码整数运算,而无需将输入中的高位置零?,assembly,binary,x86,integer,twos-complement,Assembly,Binary,X86,Integer,Twos Complement,在汇编编程中,很常见的一种情况是,需要从寄存器的低位计算某些东西,而这些低位不能保证将其他位归零。在像C这样的高级语言中,您只需将输入转换为较小的大小,然后让编译器决定是否需要将每个输入的高位分别归零,或者是否可以在事后切掉结果的高位 这在x86-64(又名AMD64)中尤其常见,原因有多种1,其中一些存在于其他ISA中 我将使用64位x86作为示例,但目的是询问/讨论一般的无符号二进制算法,因为。(注意C和C++不保证两个互补,而有符号的溢出是未定义的行为。)< /P> 作为一个例子,考虑一个

在汇编编程中,很常见的一种情况是,需要从寄存器的低位计算某些东西,而这些低位不能保证将其他位归零。在像C这样的高级语言中,您只需将输入转换为较小的大小,然后让编译器决定是否需要将每个输入的高位分别归零,或者是否可以在事后切掉结果的高位

这在x86-64(又名AMD64)中尤其常见,原因有多种1,其中一些存在于其他ISA中

我将使用64位x86作为示例,但目的是询问/讨论一般的无符号二进制算法,因为。(注意C和C++不保证两个互补,而有符号的溢出是未定义的行为。)< /P>

作为一个例子,考虑一个简单的函数,可以编译到<代码> LEA<代码>指令2中。(在x86-64 SysV(Linux)3中,前两个函数参数位于

rdi
rsi
中,在
rax
中返回的
int
是32位类型。)

gcc知道,即使是负符号整数的加法,也只能从右向左进行,因此输入的高位不会影响进入
eax
的内容。因此,
leaeax,[rdi+rsi*4+3]

还有哪些操作具有不依赖于输入高位的结果低位特性? 为什么它会起作用



脚注 1为什么经常出现这种情况: x86-64具有可变长度指令,其中额外的前缀字节会更改操作数大小(从32更改为64或16),因此在以相同速度执行的指令中通常可以保存字节。在写入寄存器的低8b或16b时(或稍后读取完整寄存器(Intel pre IvB)时暂停),它也有错误的依赖项(AMD/P4/Silvermont):出于历史原因。几乎所有的算术和逻辑都可以在通用寄存器的低位8、16或32位以及全64位上使用。整数向量指令也是相当非正交的,有些操作对于某些元素大小不可用

此外,与x86-32不同,ABI在寄存器中传递函数参数,对于窄类型,高位不需要为零

2 LEA:与其他指令一样,的默认操作数大小为32位,但默认地址大小为64位。操作数大小前缀字节(
0x66
REX.W
)可以使输出操作数大小为16或64位。地址大小前缀字节(
0x67
)可以将地址大小减少到32位(64位模式)或16位(32位模式)。因此,在64位模式下,
leaeax[edx+esi]
leaeax[rdx+rsi]
多占用一个字节

可以执行
lea-rax、[edx+esi]
,但地址仍然只计算32位(进位不设置
rax
的32位)。使用
leaeax、[rdx+rsi]
可以获得相同的结果,即缩短两个字节。因此,地址大小前缀在
LEA
中永远不会有用,Agner Fog优秀的objconv反汇编程序反汇编输出中的注释警告了这一点

3 x86 ABI: 调用方不必将用于按值传递或返回较小类型的64位寄存器的上部归零(或符号扩展)。想要将返回值用作数组索引的调用方必须对其进行签名扩展(使用
movzx-rax、eax
,或eax指令的特例
cdqe
(不要与
cdq
混淆,后者将
eax
扩展为
edx:eax
,例如设置
idiv

这意味着返回
unsigned int
的函数可以在
rax
中以64位临时形式计算其返回值,并且不需要
rax
mov eax、eax
。这种设计决策在大多数情况下都很有效:调用方通常不需要任何额外的指令来忽略
rax
上半部分中的未定义位


4 C和C++

C和C++特别不需要两个补二进制符号整数(除)。因此,对于完全可移植的C,这些技巧只对

无符号类型有用。显然,对于有符号操作,例如,在符号/幅度表示中设置符号位意味着减去其他位,而不是相加。我还没有研究过补语的逻辑

然而,这是因为实际上没有人关心其他任何事情。许多使用二的补码的东西也应该使用一的补码,因为符号位仍然不会改变其他位的解释:它只是有一个值-(2N-1)(而不是2N)。符号/幅值表示不具有此属性:根据符号位的不同,每个位的位置值为正或负

还请注意,C编译器可以假设签名溢出从未发生,因为它是未定义的行为。比如说。这使得在可与高位垃圾一起使用的C..

范围的操作中检测有符号溢出非常不方便:
  • 位逻辑
  • 左移(包括
    [reg1+reg2*scale+disp]
    中的
    *scale
  • 加法/减法(因此
    LEA
    指令:不需要地址大小前缀。如果需要,只需使用所需的操作数大小进行截断即可。)
  • 乘法的下半部分。e、 g.16b x 16b->16b可以用32b x 32b->32b完成。您可以使用32位的imul r32、r/m32、imm32,然后只读取结果的低位16。(但是,如果使用
    m32
    版本,请小心使用更宽的内存引用。)

    正如Intel的insn ref手册所指出的,2和3操作数形式
    ; int intfunc(int a, int b) { return a + b*4 + 3; }
    intfunc:
        lea  eax,  [edi + esi*4 + 3]  ; the obvious choice, but gcc can do better
        ret
    
     0x801F
    -0x9123
    -------
     0xeefc
    
        *Warning*: This diagram is probably slightly bogus.
    
    
           ABCD   A has a place value of -2^3 = -8
         * abcd   a has a place value of -2^3 = -8
         ------
       RRRRrrrr
    
       AAAAABCD * d  sign-extended partial products
     + AAAABCD  * c
     + AAABCD   * b
     - AABCD    * a  (a * A = +2^6, since the negatives cancel)
      ----------
              D*d
             ^
             C*d+D*c