C 将无符号字符强制转换为有符号数据类型时,为什么在程序集中使用movzbl?
我正在学习汇编中的数据移动(C 将无符号字符强制转换为有符号数据类型时,为什么在程序集中使用movzbl?,c,assembly,casting,mov,zero-extension,C,Assembly,Casting,Mov,Zero Extension,我正在学习汇编中的数据移动(MOV)。 我试图编译一些代码,以便在x86_64 Ubuntu 18.04机器中查看程序集: typedef unsigned char src_t; typedef xxx dst_t; dst_t cast(src_t *sp, dst_t *dp) { *dp = (dst_t)*sp; return *dp; } 其中,src\u t是无符号字符。至于dst,我尝试了char、short、int和long。 结果如下所示: // type
MOV
)。我试图编译一些代码,以便在x86_64 Ubuntu 18.04机器中查看程序集:
typedef unsigned char src_t;
typedef xxx dst_t;
dst_t cast(src_t *sp, dst_t *dp) {
*dp = (dst_t)*sp;
return *dp;
}
其中,src\u t
是无符号字符
。至于dst
,我尝试了char
、short
、int
和long
。
结果如下所示:
// typedef unsigned char src_t;
// typedef char dst_t;
// movzbl (%rdi), %eax
// movb %al, (%rsi)
// typedef unsigned char src_t;
// typedef short dst_t;
// movzbl (%rdi), %eax
// movw %ax, (%rsi)
// typedef unsigned char src_t;
// typedef int dst_t;
// movzbl (%rdi), %eax
// movl %eax, (%rsi)
// typedef unsigned char src_t;
// typedef long dst_t;
// movzbl (%rdi), %eax
// movq %rax, (%rsi)
我想知道为什么在每种情况下都使用movzbl
?它不应该对应于dst?
谢谢 如果您想知道为什么不将
movzbw(%rdi),%ax
用于short
,这是因为写入8位和16位部分寄存器必须与以前的高字节合并
写入32位寄存器(如EAX)会隐式地将零扩展到完整RAX,从而避免对RAX的旧值或任何ALU合并uop的错误依赖。()
在x86上加载字节的“正常”方式是使用movzbl
或movsbl
,这与在RISC机器上(如ARMldrb
或ldrsb
或MIPSlbu
lb
相同
GCC通常避免的奇怪的CISC操作是与旧值合并,只替换低位,如movb(%rdi),%al
。Clang更鲁莽,更经常地编写部分reg,而不仅仅是为商店读取它们。当dst
为signed char
时,您很可能会看到叮当声加载到%al
中并存储
如果您想知道为什么不
movsbl(%rdi),%eax
(符号扩展名)
源值是无符号的,因此根据C语义,零扩展(非符号扩展)是扩展它的正确方法。要获取movsbl
,您需要返回(int)(签名字符)c
在*dp=(dst)*sp代码>转换到dst
的转换已经从分配到*dp
中隐式显示
无符号字符的值范围为0..255(在x86上,字符位=8)。
零将其扩展到有符号整数
可以产生一个从0..255
的值范围,即将每个值保留为有符号非负整数
将其扩展到有符号整数的符号将产生一个从-128..+127
的值范围,从而更改无符号字符的值>=128。这与C语言的语义冲突,因为C语言的语义扩大了转换范围,保留了值
它不应该对应于dst
它必须至少与dst一样宽。事实证明,通过使用movzbl
(通过隐式零扩展写入32位reg来处理顶部的32位)扩展到64位是最有效的扩展方式
存储到*dp
是一个很好的演示,asm用于宽度不是32位的dst
无论如何,请注意,只有一个转换发生。您的src_t
在al/ax/eax/rax中通过加载指令转换为dst
,并存储到任意宽度的dst。并将其保留为返回值
零扩展负载是正常的,即使您只是要读取该结果的低字节。其中
src\u t
是无符号字符
当您将char
指针转换到另一种类型时,如果源内存实际上不是您转换到的类型,则表示您违反了,您也可能违反了系统施加的任何对齐限制,例如。@AndrewHenle:OP将*sp
值转换为int
,而不是将sp
指针转换为int*
。它没有严格的别名。从第二个代码块中,我们可以看到typedef unsigned char src\t代码>我认为第一个代码块中的无符号(int)是一个输入错误。我修正了这个问题如何表达的错误。@fuz:当dst
是char
时,返回值实际上只是al
。x86-64 System V ABI指定返回值寄存器的高位可以保存垃圾。(即使是clang所依赖的不成文约定也只适用于args,而不适用于返回值。)我在这里发布了一个答案,从我能想到的所有可能的角度解决了这个问题:P@AndrewHenle:(编辑:我键入此内容时,您删除了您的评论)。为什么您假设调用方传递的不是dst
对象的地址作为第二个参数?这显然也不是OP所要问的;生成的asm对于no UB情况(必须如此)是有意义的,这就是被询问的内容,而不是内联到某个未指定的调用方。@PeterCordes没有提供上下文。这更多的是一个“小心”的警告,因此它只是一个评论。通常,彼得·科尔德斯的回答可以从技术问题的前几行中辨认出来。