C++左移操作的陷阱

发现的过程十分曲折,在这里不做赘述。为了说明问题直接上代码。

#include <iostream>
int main() {
  int off = 3;
  int64_t a = (1 << off);
  off = 35;
  int64_t b = (1 << off);
  if (a == b) {
    std::cout << "There is a bug.\n";
  }
  return 0;
}

这段代码乍看上去是不会打印if判断中的字符串的,但是实验后就会发现,“There is a bug”的字样清晰的打印了出来。这是为什么呢?
为了找到原因,将这段代码的汇编拿出来看下。

        movl    $3, -20(%rbp)
        movl    -20(%rbp), %ecx
        movl    $1, %eax
        sall    %cl, %eax
        cltq
        movq    %rax, -16(%rbp)
        movl    $35, -20(%rbp)
        movl    -20(%rbp), %ecx
        movl    $1, %eax
        sall    %cl, %eax
        cltq
        movq    %rax, -8(%rbp)
        movq    -16(%rbp), %rax
        cmpq    -8(%rbp), %rax
        jne     .L11
        movl    $.LC0, %esi
        movl    $_ZSt4cout, %edi
        call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc

我们看到,汇编在进行左移操作时,是将移动的位数存放在CL寄存器中,然后使用sall左移指令。这里特别说明下寄存器的名称与实际寄存期间的对应关系:RAX是一个64bit寄存器,EAX表示RAX的低32bit,是一个32bit寄存器,AX表示EAX的低16bit,是一个16bit寄存器,AH、AL分别表示AX的高8bit与低8bit,是两个8bit寄存器。RBX、RCX等也是同样的道理。
可是,为什么好好的数字35,放到了寄存器里面就变成和数字3一样了呢?
在网上查阅了很多资料,最终结合Intel的时令集手册和实验发现问题原来出在指令sall上面。代码中由于1没有指明类型,编译器默认当作32bit处理,对32bit的数进行左移,会使用sall(对于小于32bit的数也是使用sall,但对于64bit数字会使用salq,后面说明),sall指令有一个不为人知的小秘密,就是他会将存放在CL寄存器中的8bit数据进行一个低5bit的Mask。也就是说虽然存进去的是35,但是sall只看低5bit,35&0x1F=3。问题就出在这里。
那如果是64bit的数字呢?实验证明,对64bit的数据进行左移,编译器会使用salq指令,这个指令虽然强,但是Intel也只是把mask的位数由5bit调整为了6bit。如果位移的位数超过了64,一样会出现bug。
这告诉我们,使用位移操作的时候,移动的位数一定不要超过该数字类型的bit位的数量,否则小bug会带来巨大的损失,最关键是还很难定位。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值