首页 > 其他分享 >16位数和32位数进行运算时的细节

16位数和32位数进行运算时的细节

时间:2022-12-18 18:44:22浏览次数:40  
标签:16 32 mov rbp eax uint16 位数 movzx

1. 概览

  • 16位的数据参与运算/赋值时,在数据加载/存储阶段会有movzx指令参与,在加载16位数据到32位寄存器过程中,将高32位清0

  • 如果16位数的运算结果存在隐转或者显示转换到32位时,编译器就认为这个运算结果是一个32位数,不用使用movzx对高位进行清0

2. 正文

第一个程序: uint16相减时,在计算机底层发生了什么运算呢

int main(){
        uint16_t b = 2;
        uint16_t c = 3;
        uint16_t a = b - c; // 65535
        return 0;
}

反汇编可以看到,cpu仍以32位寄存器去做计算,只是加载数据的时候使用mozx清0了寄存器

最终的计算结果从eax放入到栈变量的时候,直接截断,将ax放入, 最终就得到了一个65535

   0x00000000004006ad <+0>:     push   rbp
   0x00000000004006ae <+1>:     mov    rbp,rsp
   0x00000000004006b1 <+4>:     mov    WORD PTR [rbp-0x6],0x2
   0x00000000004006b7 <+10>:    mov    WORD PTR [rbp-0x4],0x3
 =>0x00000000004006bd <+16>:    movzx  eax,WORD PTR [rbp-0x4]
 =>0x00000000004006c1 <+20>:    movzx  edx,WORD PTR [rbp-0x6]
 =>0x00000000004006c5 <+24>:    sub    edx,eax
 =>0x00000000004006c7 <+26>:    mov    eax,edx
 =>0x00000000004006c9 <+28>:    mov    WORD PTR [rbp-0x2],ax
   0x00000000004006cd <+32>:    mov    eax,0x0
   0x00000000004006d2 <+37>:    pop    rbp
   0x00000000004006d3 <+38>:    ret 

第二个程序: uint16_t 赋值给32位数时候会发生什么

int main(){
        uint16_t b = 2;
        int num = b;			 //2
        uint32_t num2 = b; //2
        return 0;
}

再次从反汇编看到,会通过movzx的方式加载16位数据到32位寄存器中,因为寄存器的高16位就被清0了

   0x00000000004006ad <+0>:     push   rbp
   0x00000000004006ae <+1>:     mov    rbp,rsp
   0x00000000004006b1 <+4>:     mov    WORD PTR [rbp-0xa],0x2
=> 0x00000000004006b7 <+10>:    movzx  eax,WORD PTR [rbp-0xa]
   0x00000000004006bb <+14>:    mov    DWORD PTR [rbp-0x8],eax
=> 0x00000000004006be <+17>:    movzx  eax,WORD PTR [rbp-0xa]
   0x00000000004006c2 <+21>:    mov    DWORD PTR [rbp-0x4],eax
   0x00000000004006c5 <+24>:    mov    eax,0x0
   0x00000000004006ca <+29>:    pop    rbp
   0x00000000004006cb <+30>:    ret  

两个测试下来能够发现,16位的数据参与运算/赋值时,都会有movzx指令参与,在加载16位数据到32位寄存器过程中,将高32位清0

第三个程序, 计算结果存在隐式转换的时候,会发生什么呢

int main(){
        uint32_t a;
        uint16_t b = 2;
        uint16_t c = 3;
        a = b - c; 		//4294967295
        return 0;
}

这应该是我们最关心的情况了,毕竟前面两种情况哪怕是没有基础也能想象的到,但是对于这种情况,能猜的到,却不知道如何发生的,往往会误解为:无符号减法发生溢出的时候,从低位转高位会补1

其实不是的,而是发生了隐转,也就是说上面的程序等价于

int main(){
        uint32_t a;
        uint16_t b = 2;
        uint16_t c = 3;
        a = static_cast<uint32_t>(b - c); 		//4294967295
        return 0;
}

那么这个时候就会告诉编译器,这两个uint16_t 的减法的结果是32位数,不使用movzx去做高位清0了

   0x00000000004006ad <+0>:     push   %rbp
   0x00000000004006ae <+1>:     mov    %rsp,%rbp
   0x00000000004006b1 <+4>:     movl   $0x2,-0x4(%rbp)
   0x00000000004006b8 <+11>:    movw   $0x2,-0x8(%rbp)
   0x00000000004006be <+17>:    movw   $0x3,-0x6(%rbp)
   0x00000000004006c4 <+23>:    movzwl -0x8(%rbp),%edx
   0x00000000004006c8 <+27>:    movzwl -0x6(%rbp),%eax
   0x00000000004006cc <+31>:    sub    %eax,%edx
   0x00000000004006ce <+33>:    mov    %edx,%eax
=> 0x00000000004006d0 <+35>:    mov    %eax,-0x4(%rbp) //此处没有使用movzx
   0x00000000004006d3 <+38>:    mov    $0x0,%eax
   0x00000000004006d8 <+43>:    pop    %rbp
   0x00000000004006d9 <+44>:    ret 

所以从这个case,我们能知道: **uint16_t的运算结果存在隐转或者显示转换到32位时,编译器就认为这个运算结果是一个32位数,不使用movzx对高位进行清0 **

为什么探究这个问题呢,因为看到一段代码, 因为使用了static_cast<uint16_t> 屏蔽了隐式转换成int,导致需要做分类处理

    uint16_t sequence_number = ..., 
    uint16_t last_seq_no_ = ...;
    int iat_packets = ...;
		
    if (IsNewerSequenceNumber(sequence_number, last_seq_no_ + 1)) {
      iat_packets -= static_cast<uint16_t>(sequence_number - last_seq_no_ - 1);
      iat_packets = std::max(iat_packets, 0);
    } else if (!IsNewerSequenceNumber(sequence_number, last_seq_no_)) {
      iat_packets += static_cast<uint16_t>(last_seq_no_ + 1 - sequence_number);
    }

实际可以改成这样

    uint16_t sequence_number = ..., 
    uint16_t last_seq_no_ = ...;
    int iat_packets = ...;
		
    iat_packets -= (sequence_number - last_seq_no_ - 1);
    iat_packets = std::max(iat_packets, 0);

标签:16,32,mov,rbp,eax,uint16,位数,movzx
From: https://www.cnblogs.com/ishen/p/16990756.html

相关文章