为什么`(char)~0`和`(unsigned char)~0`返回不同宽度的值?

4

在尝试打印UTF-8字符的组成字节值时,我遇到了这个问题。

这是我编写的用于测试各种~0操作的程序:

#include <stdio.h>

int main()
{
    printf("%x\n", (char)~0); // ffffffff
    printf("%x\n", (unsigned char)~0); // ff
    printf("%d\n", sizeof(char) == sizeof(unsigned char)); // 1
    printf("%d\n", sizeof(char) == sizeof(unsigned int)); // 0
    printf("%d\n", (char)~0 == (unsigned int)~0); // 1
}

我不太明白为什么 char 会生成一个 int 大小的值,而 unsigned char 会生成一个 char 大小的值。


1
%x 期望一个 unsigned int。所以当你传入 -1 时,它会被转换为最大的 unsigned int(在2's补码机器上)。我不知道这是否是标准,还是只发生在这里。使用 %hhx 可以做正确的事情。但使用无符号类型会更有意义。 - ikegami
1
如果char是有符号的,那么(char)~0可能会被转换为(char)-1。通过默认参数提升(char)-1会被转换为(int)-1 - Ian Abbott
你不能直接将 char 传递给 printf()。在调用函数的过程中,它会自动转换为 int。当 char 是有符号的(例如在您的实现中),(char)~0 是一个负值。当一个负值被重新解释为 unsigned int(当 printf() 处理 "%x" 时),它在最高有效位上有一堆二进制 1 - pmg
1
我之前的评论有一个更准确的版本:%x 需要一个 unsigned int。所以你传递的 -1(由于整数提升而成为 int)被解释为 unsigned int,在 2 的补码机器上得到最大的 unsigned int。使用 %hhx 可以做正确的事情。但是使用无符号类型(例如 unsigned char)会更有意义。 - ikegami
@EricPostpischil 如果假设使用二进制补码,~0 将产生 (int)-1,因此将在有符号 char 的范围内。 - Ian Abbott
@IanAbbott:啊,对了。 - Eric Postpischil
3个回答

8

当向像printf这样的变参函数传递比int更小的类型时,它会被提升int类型。

在第一种情况下,您正在传递值为-1的char,其表示(假设为2的补码)为0xff。这被提升为值为-1且表示为0xffffffff的int,因此打印出来的就是这个数。

在第二种情况下,您正在传递值为255且表示为0xff的unsigned char。这被提升为值为255且表示为0x000000ff的int,因此打印出来的就是这个数(不带前导零)。


当像这样解释时,它完全有意义,这是算术提升,而不是位运算。我根本没有考虑过这一点。有符号字符-1被转换为有符号整数-1,并被视为无符号整数进行打印。 - Marcus Harrison

2
他们不会产生不同宽度的值,而是会产生具有不同设置比特数的值。
在您的C实现中,看起来int是32位的且char是有符号的。在本答案中,我将使用这些内容,但读者应该注意,C标准允许其他选择。
我将使用十六进制来表示代表值的位。
(char)~0中,0是一个int。然后,~0具有位FFFFFFFF。在32位二进制补码int中,这代表-1。(char)将其转换为char
此时,我们有一个值为-1的char,用FF表示。当作为参数传递给printf时,它会自动转换为int。由于其值为-1,因此将其转换为值为-1的int。表示int的位为FFFFFFFF。您要求printf使用%x格式化它。技术上讲,这是个错误;%x是用于unsigned int的,但是您的printf实现将FFFFFFFF的位格式化为unsigned int,生成输出“ffffffff”。
(unsigned char)~0)中,~0的值再次为-1,用位FFFFFFFF表示,但现在转换为unsigned char。转换为无符号整数类型后,会对模数M进行包装,其中M是该类型的最大值加1,因此对于8位的unsigned char来说是256。从数学上讲,转换为-1 + 1•256=255,这是开始值加上将值带入unsigned char范围所需的256倍数。结果是255。实际上,它是通过取低八位来实现的,因此FFFFFFFF变成FF。但是,在unsigned char中,位FF代表255而不是-1。
现在我们有一个值为255的unsigned char,用FF表示。传递给printf的结果会自动转换为int。由于其unsigned char值为255,因此将其转换为int的结果为255。当您要求printf使用%x格式化它时(如上所述是错误的),printf会将其格式化为位unsigned int,生成输出“ff”。

1
在这两个调用中
printf("%x\n", (char)~0); // ffffffff
printf("%x\n", (unsigned char)~0); // ff

表达式 (char)~0)(unsigned char)~0) 由于整数提升而转换为类型 int
在使用的系统中,类型 char 的行为类似于类型 signed char。因此,在将表达式提升为类型 int 时,符号位会被传播。
另一方面,在整数提升之前,由于转换为无符号类型,表达式 (unsigned char)~0 具有类型 unsigned char。因此,在将表达式提升为类型 int 时,两者的符号位都不会传播。
注意,转换说明符 x 适用于类型 unsigned int 的对象。因此,第一个 printf 调用应该写成:
printf("%x\n", ( unsigned int )(char)~0);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接