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