为何MOVZX r64,r/m8表现同MOVZX r32,r/m8?MOVSX表现符合预期
测试代码与现象
无符号赋值的C代码:
int main() { unsigned long int ui64{}; unsigned char ui8{ 0xAA }; unsigned short ui16 { 0xBBBB}; ui64 = ui8; ui64 = ui16; }
生成的汇编使用0F B6 /r MOVZX r32, r/m8和0F B7 /r MOVZX r32, r/m16,而非64位版本的REX.W + 0F B6 /r MOVZX r64, r/m8、REX.W + 0F B7 /r MOVZX r64, r/m16。
有符号赋值的C代码:
int main() { long int i64{}; char i8{ 1 }; short i16 {2}; i64 = i8; i64 = i16; }
生成的汇编则使用预期的64位符号扩展指令REX.W + 0F BE /r MOVSX r64, r/m8和REX.W + 0F BF /r MOVSX r64, r/m16。
核心原因
这种差异源于x86-64架构的寄存器行为特性,以及无符号、有符号扩展的本质区别:
无符号扩展(MOVZX)的场景
x86-64架构规定:当对32位通用寄存器(如EAX、EBX)执行写入操作时,对应的64位寄存器(如RAX、RBX)的高32位会被自动清零。
对于无符号类型的赋值,将unsigned char/unsigned short零扩展到32位后,再写入64位寄存器,最终的64位值和直接零扩展到64位的结果完全一致(因为无符号值的高32位本身就是0)。
同时,32位版本的MOVZX指令(3字节)比64位版本(4字节)更短,能减少指令长度、提升指令缓存利用率,因此编译器会选择更高效的32位MOVZX指令。有符号扩展(MOVSX)的场景
有符号扩展的核心是要保持数值的符号位一致性:如果原始值是负数,扩展后的高比特位必须全部为1。
如果先将char/short符号扩展到32位,再写入64位寄存器,x86-64的自动清零高32位特性会破坏符号扩展的结果——比如负数的32位扩展值是0xFFFFFFFF,写入64位寄存器后会变成0x00000000FFFFFFFF,这显然不是正确的64位有符号值(正确应为0xFFFFFFFFFFFFFFFF)。
因此编译器必须直接使用64位版本的MOVSX指令,一次性将8/16位有符号值符号扩展到64位,保证结果的正确性。
内容的提问来源于stack exchange,提问作者rand_program

