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机器上(如ARM
ldrb
ldrsb
或MIPS
lbu
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没有提供上下文。这更多的是一个“小心”的警告,因此它只是一个评论。通常,彼得·科尔德斯的回答可以从技术问题的前几行中辨认出来。