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

评论

嗨,欢迎来到RE.SE。考虑到优化器在将源代码转换为二进制的过程中所扮演的角色,这是一个相当棘手的问题,除非您也提供感兴趣的编译器(和版本)的详细信息,否则将很难回答。现在我的猜测还在于这些是常用的IA-32助记符,但您可能还需要指出ISA。

#1 楼

首先应该注意的是,那里有太多架构,每种架构都有自己的指令集。在这里,我假设您的意思是x86(并且您确实应该将正确的架构标记为上述的0xC0000022L)。以下答案的大多数部分也将适用于其他体系结构,但是它们可能使用不同的助记符或缺少某些提及的指令。
它们只是同一操作码的别名,因为向左移动总是用0填充空位。在C中,SAL会向左移,并且该移位指令是打印为SAL还是SHL取决于编译器/反汇编程序
OTOH有两种版本的右移,因为您可以用0填充移出的位(逻辑移位)或旧值的高位(算术移位)。 SHL进行算术移位,<<进行逻辑移位。在C中,用于右移的运算符是SAR,但是规则取决于类型的符号性: >对有符号类型的右移是由实现定义的,即编译器可以选择进行算术或逻辑移位。但是,几乎所有现代编译器都会对有符号类型进行算术移位(SHR)(否则进行算术移位将非常棘手/笨拙)。某些编译器可能会提供选项,尽管可以选择正确的shift变体

根据C99标准,第6.5.7节:

整数提升是对每个操作数执行的。结果的类型是提升后的左操作数的类型。如果右操作数的值为负或大于或等于提升后的左操作数的宽度,则行为是不确定的。
E1 << E2的结果是E1左移E2位位置;空位用零填充。如果E1具有无符号类型,则结果的值为E1×2E2,与结果类型中可表示的最大值相比,以模方式减少了1。如果E1具有带符号的类型和非负值,并且E1×2E2可表示为结果类型,则为结果值;
E1 >> E2的结果是E1右移E2位位置。如果E1具有无符号类型或E1具有带符号类型和非负值,则结果的值是E1 / 2E2商的整数部分。如果E1具有带符号的类型和负值,则结果值是实现定义的。


但是,还有许多其他操作可以产生移位指令,并且各种情况下移位运算符不会产生移位指令
看到未编译为移位指令的>>很少见,但编译器可能会将SHR优化为SAR,您会看到类似<</>>x << 1的内容。在x86上,i≤3的x += x也可以编译为ADD eax, eax而不是shift。当然,在没有移位的假设架构上也可以输出LEA ecx, [eax + eax],或者移位比乘法慢
或GCC识别x << i及其逆条件LEA eax, [eax*2ⁱ]并将它们分别优化为MUL x, 2(x << 1) + (x << 4) + (x << 13),因此(将来)有可能将它们等效的(a ^ b) + (a & b) + (a & b)(a + b) - (a & b) - (a & b)转换为a + ba ^ b完全没有变化。查看它们的作用
对于其他情况,有各种示例:


2的乘方乘以左移来完成。在x86上,存在更通用的(a ^ b) + ((a & b) << 1)指令,因此对于指数⩽8,(a + b) - ((a & b) << 1)ADD之间的选择取决于编译器。数组算术也需要大量的乘法运算,因此通常也使用移位

与许多其他常数的乘法运算也可以优化为一系列ADD / SUB并进行移位,如果比XOR指令本身快的话。再次在x86中,偶尔使用LEA代替移位

除以2的幂是通过向右移位完成的。对于无符号类型,这是一个简单的逻辑转变。对于有符号类型,它是一个算术移位,然后是其他一些移位和ADD来纠正结果(因为除法运算轮次取零,而算术右移轮次取值-inf)

按常数的除法将优化为乘以相应的乘法逆,可能会涉及一些移位以舍入结果

如果从Sandy Bridge调整微体系结构,则即使对非常量进行除数,Clang甚至会发出LEA用于检查高位onward

当然,在没有像x86这样的有效位域操纵的情况下,位域访问当然需要在体系结构中进行大量转换。请参见demo

...

这里是mul / div示例的一些说明。您可以轻松地看到LEASHL取代了,而MULSHRx*15x*16 - x完成了。此外,根据编译器,x*33被优化为x*32 + x(x << 4) - x。助记符(x << 5) + xx*8也可以由编译器自由选择
我还放置了一些非x86编译器进行比较,因为它们没有lea eax, [0+rdi*8],但可能具有其他与移位相关的指令或除正常移位指令之外的其他移位功能。您可以在各种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);
}

编译为
彩色代码行
摘要:当今的编译器确实很聪明,并且可以向普通人输出“令人惊讶”的结果。它们可以为C中的几乎所有运算符发出移位指令。通过优化编译器,所有赌注都关闭了。
另请参阅

移位运算符(<<,>>)是算术运算符还是C中是否逻辑?
有符号整数上的算术移位
c中负数的逻辑右移的实现
有符号右移:哪个编译器使用逻辑移位
按位移位运算符在MSVC中
在GCC中按位移位
移位运算符-cppreference


#2 楼

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由于有符号的无符号差异而不同

评论


另外:通过优化进行除法和乘法:en.wikipedia.org/wiki/Multiplication_algorithm#Shift_and_add

–诺德瓦尔德
19年1月31日在7:01

我注意到在我的情况下产生了2 * 30:shl dword ptr [rbp-4],1

– PaHa
19 Mar 25 '19在15:40