哪些运算符使用 sal、shl、sar 或 shr

逆向工程 部件 C
2021-06-23 09:32:29

C 语言中的哪些运算符会产生汇编命令sal, shl, sar or shr,例如?

2个回答

首先应该注意的是,有很多架构,每个架构都有自己的指令集我假设你的意思是(并且您确实应该将正确的架构标记为上面所说的 0xC0000022L)。以下答案的大部分内容也适用于其他架构,但它们可能使用不同的助记符或缺少一些提到的说明

SAL并且SHL是一样的。它们只是相同操作码的别名,因为左移总是用 0s 填充空位在 C 中<<会做左移,而移位指令是打印为SAL还是SHL取决于编译器/反汇编器

OTOH 有两种右移版本,因为您可以用零逻辑移位或旧值的高位算术移位填充移出的位SAR进行算术移位并SHR进行逻辑移位。在 C 中,右移运算符是>>,但规则取决于类型的符号:

  • 无符号类型的右移始终是逻辑移位,因此SHR将使用
  • 有符号类型的右移是实现定义的,即编译器可以选择进行算术或逻辑移位。然而,几乎所有现代编译器都会SAR对有符号类型进行算术移位 ( ) (否则进行算术移位会太棘手/笨拙)。一些编译器可能有一个选项来选择右移变体

根据C99 标准,第 6.5.7 节:

对每个操作数执行整数提升。结果的类型是提升的左操作数的类型。如果右操作数的值为负或大于或等于提升的左操作数的宽度,则行为未定义。

E1 << E2的结果E1左移E2位位置;空出的位用零填充。如果E1具有无符号类型,则结果的值为E1 × 2 E2,比结果类型中可表示的最大值减少模 1。如果E1具有有符号类型和非负值,并且E1 × 2 E2在结果类型中可表示,则这就是结果值;否则,行为未定义。

E1 >> E2的结果E1右移E2位位置。如果E1具有无符号类型或者如果E1具有有符号类型和非负值,则结果的值是E1 / 2 E2的商的整数部分如果E1具有有符号类型和负值,则结果值是实现定义的。


然而,还有很多其他操作可以产生移位指令,以及移位运算符不产生移位指令的各种情况

不太常见的<</>>是未编译为 shift 指令,但编译器可能会优化x << 1x += x,您会看到诸如ADD eax, eax或 之类的东西LEA ecx, [eax + eax]在 x86 上x << i, i ⩽ 3 也可以编译为LEA eax, [eax*2ⁱ]而不是移位。当然,MUL x, 2在没有移位或移位比乘法慢的假设架构上也可以得到类似的输出

编译器还能够将复杂的语句(x << 1) + (x << 4) + (x << 13)转换为更简单的语句,例如单次乘以 8210,不再需要移位。
或者 GCC 识别(a ^ b) + (a & b) + (a & b)及其逆条件(a + b) - (a & b) - (a & b)并将它们分别优化为a + ba ^ b,因此(在未来)它们有可能将等价物(a ^ b) + ((a & b) << 1)(a + b) - ((a & b) << 1)转换为ADD和 ,XOR而根本没有任何转换。
看到他们在行动

对于另一种情况,有各种示例:

  • 乘以 2 的幂是通过左移完成的在 x86 上存在更通用的LEA指令,因此对于指数 ⩽ 8 之间的选择LEASHL取决于编译器。数组运算也需要大量的乘法,所以通常也使用移位
  • 乘以许多其他常数也可以优化为一系列 ADD/SUB 和移位,如果这比MUL指令本身快的话再次在 x86 中偶尔使用 LEA 代替移位
  • 除以 2 的幂是通过右移完成的对于无符号类型,这是一个简单的逻辑转换。对于有符号类型,它是一个算术移位,然后是一些其他移位和 ADD 以更正结果(因为除法向零舍入,而算术右移向 -inf 舍入)
  • 除以常数将被优化为乘以相应乘法逆的乘法,这可能涉及一些移位以四舍五入结果。
  • SHR如果从 Sandy Bridge 开始调整微体系结构,Clang 甚至会发出一个检查高位的信号,同时通过一个非常数进行除法
  • 位域访问当然需要在架构中使用大量移位,而没有像 x86 这样的高效位域操作。看演示
  • ...

以下是 mul/div 示例的一些插图你可以很容易地看到x*15被替换x*16 - x,并x*33通过完成x*32 + x,即(x << 4) - x(x << 5) + x此外,x*8针对编译器进行优化lea eax, [0+rdi*8]shl edi, 3取决于编译器。助记符SALSHL也由编译器自由选择

我还放了一些非 x86 编译器进行比较,因为它们没有LEA但可能有其他与移位相关的指令或除了正常的移位指令之外的不同移位功能。您可以在各种 x86 和非 x86 编译器之间进行更改,以查看其输出之间的差异。另一个结合了我上面所说的多项内容的示例:

struct bitfield {
    int x: 10;
    int y: 12;
    int z: 10;
};

int f(bitfield b)
{
    int i = b.x*65;
    int j = b.y/25;
    int k = b.z/8;
    return (i << j) + (k >> j);
}

这编译为

f(bitfield):
        mov     eax, edi
        mov     edx, edi
        sar     edi, 22
        sal     eax, 10
        sal     edx, 6
        sar     eax, 20
        sar     dx, 6
        imul    ecx, eax, 5243
        sar     ax, 15
        sar     ecx, 17
        sub     ecx, eax
        movsx   eax, dx
        mov     edx, eax
        movsx   ecx, cx
        sal     edx, 6
        add     edx, eax
        lea     eax, [rdi+7]
        sal     edx, cl
        test    di, di
        cmovns  eax, edi
        sar     ax, 3
        cwde
        sar     eax, cl
        add     eax, edx
        ret

你可以打开Godbolt链接,看看哪条指令对应哪一行彩色代码

总结:现在的编译器真的很聪明,可以向普通人输出“令人惊讶”的结果。他们几乎可以为 C 中的任何运算符发出移位指令。使用优化编译器,一切都结束了

也可以看看

int main (void){
    unsigned int    uin =  0x1000;
    signed int      sin = -0x1000;
   return (uin<<8)+(uin>>8)+(sin<<8)+(sin>>8);   
}

编译并链接到

cl /Zi /W4 /Od /analyze /nologo salsaar.cpp /link /release

拆解

:\>cdb -c "uf salsaar!main;q" salsaar.exe | grep -A 20 Reading
0:000> cdb: Reading initial command 'uf salsaar!main;q'
salsaar!main:
01121000 55              push    ebp
01121001 8bec            mov     ebp,esp
01121003 83ec08          sub     esp,8
01121006 c745fc00100000  mov     dword ptr [ebp-4],1000h
0112100d c745f800f0ffff  mov     dword ptr [ebp-8],0FFFFF000h
01121014 8b45fc          mov     eax,dword ptr [ebp-4]
01121017 c1e008          shl     eax,8
0112101a 8b4dfc          mov     ecx,dword ptr [ebp-4]
0112101d c1e908          shr     ecx,8
01121020 03c1            add     eax,ecx
01121022 8b55f8          mov     edx,dword ptr [ebp-8]
01121025 c1e208          shl     edx,8
01121028 03c2            add     eax,edx
0112102a 8b4df8          mov     ecx,dword ptr [ebp-8]
0112102d c1f908          sar     ecx,8
01121030 03c1            add     eax,ecx
01121032 8be5            mov     esp,ebp
01121034 5d              pop     ebp
01121035 c3              ret

注意 shl 和 sal 都是相同的(操作码相同并且工作相同)由于有符号的无符号差异,shr 和 sar 不同