在C语言中进行无符号类型转换

13
int a = -534;
unsigned int b = (unsigned int)a;
printf("%d, %d", a, b);

输出为-534, -534

为什么类型转换没有发生?

我希望输出结果是-534, 534


如果我修改代码为

int a = -534;
unsigned int b = (unsigned int)a;
if(a < b)
  printf("%d, %d", a, b);

它什么都没打印出来...毕竟 a 小于 b吗?


2
你可能想要查看编译器文档,看看是否有选项可以检查printf格式说明符。gcc有-Wformat选项,在编译时会警告你%d和%u的区别。 - Michael Kohne
https://dev59.com/QHVD5IYBdhLWcg3wNY1Z - Ciro Santilli OurBigBook.com
1
这个不应该有C++的标签。 - Friedrich
8个回答

15

因为您使用 %d 进行打印,应该使用 %u 来打印无符号数。由于 printf 是一个变参函数,它无法知道参数的类型,必须依靠格式说明符来确定参数类型。因此,您所做的类型转换没有效果。


2
事实上,由于使用了 %d,无符号值被 printf() 通过 reinterpret_cast 转换回有符号值。%d 表示“将该值解释为有符号整数”。 - Vlad
是的。根据C标准,未定义的行为会导致结果(不是一般的可变参数函数,而是特别针对printf函数)。 - Johannes Schaub - litb
因为涉及无符号参数的操作会使用无符号算术进行计算。因此,在比较之前,它会将a隐式转换为无符号类型。 - Tronic
3
将一个负值转换为无符号整数值并不会取反它(例如将-534变为534),而是将其转换为与原始值模UINT_MAX+1同余的最小正数。如果您使用32位无符号整数,那么-534将被转换为4294966762(在2s补码机器上,这具有与-534相同的比特表示)。 - caf
@Vlad - 它们不是使用reinterpret_cast转换的。printf()是C函数。它们只是被强制转换了。 - Chris Lutz
@Chris:我并不是指reinterpret_cast的操作,我只是指语义:二进制表示被解释为int,而不考虑参数的类型。在C++中(我们都熟悉),这种语义是通过reinterpret_cast来表达的,而在C中,强制转换是不同语义的混合体(至少从C++的角度来看)。 - Vlad

5

首先,你不需要进行强制类型转换:通过将a赋值给ba的值会被隐式转换为unsigned int。因此,你的语句等价于:

unsigned int b = a;

现在,C和C++中的unsigned整数类型的一个重要属性是它们的值始终在范围[0,max]内,其中unsigned intmax值为UINT_MAX(定义在limits.h中)。如果您分配一个不在该范围内的值,则会将其转换为该范围内的值。因此,如果该值为负数,则需要反复添加UINT_MAX+1,使其在范围[0,UINT_MAX]内。对于上面的代码,就好像我们写了:unsigned int b = (UINT_MAX + a) + 1。这与-a(534)不相等。
请注意,无论底层表示是二进制补码、一的补码还是符号-大小(或任何其他奇特的编码方式),上述内容都是正确的。可以使用以下内容查看:
signed char c = -1;
unsigned int u = c;
printf("%u\n", u);
assert(u == UINT_MAX);

在一个典型的带有4字节int的二进制补码机器上,c是0xff,u是0xffffffff。编译器必须确保当值-1被分配给u时,它会转换为等于UINT_MAX的值。
现在回到您的代码,printf格式字符串对于b是错误的。您应该使用%u。当您这样做时,您会发现它打印了UINT_MAX-534+1的值,而不是534。
当在比较运算符<中使用时,由于b是unsigned int,因此a也被转换为unsigned int。这加上之前的b = a;意味着a
假设您有一台补码机器,并且您执行以下操作:
signed char c = -1;
unsigned char uc = c;

假设机器上的char(有符号或无符号)是8位。 那么cuc将存储以下值和位模式:

+----+------+-----------+
| c  |  -1  | 11111110  |
+----+------+-----------+
| uc | 255  | 11111111  |
+----+------+-----------+

请注意,cuc的位模式不同。编译器必须确保c的值为-1,而uc的值为UCHAR_MAX,在此计算机上为255。
有关更多详细信息,请参见我在这个SO问题中的答案

4

在printf中,你的格式说明符要求打印一个有符号整数,因此底层字节被解释为有符号整数。

你应该使用%u来指定你想要一个无符号整数。

编辑:a==b在比较时为true,这是奇怪的行为,但它是完全有效的。你没有改变底层位,只是要求编译器以某种方式处理底层位。因此,按位比较得到true。

[推测]我会怀疑行为可能会因编译器实现而异 - 即,虚构的CPU可能不会对有符号和无符号数字使用相同的逻辑,因此按位比较将失败。[/推测]


1
我觉得你写的不是你想要写的 :) - static_rtti
正如所述,因为a等于b,它不是小于。底层数据是相同的。 您应该注意编译器警告,以发现这样的问题(C4018是上面代码将触发的一个)http://msdn.microsoft.com/en-us/library/y92ktdf2%28VS.80%29.aspx - Danny Parker
正如其他评论中的某人所述,比较的行为在所有C编译器/组合中都是相同的。 - Johannes Schaub - litb
Hassan,相等性不是由底层位决定的,而是因为C标准保证了这一点。即使在符号幅度编码的情况下,例如ab具有不同的位,a == b也是成立的。请查看我的答案以获取详细信息。 - Alok Singhal

1

C 有时可能会变得很丑陋。问题在于,-534 总是表示值 0xfffffdea,无论它存储在具有 unsigned int 或 signed int 类型的变量中。要比较这些变量,它们必须是相同类型的,因此一个将自动转换为 unsigned 或 signed int 以匹配另一个。一旦它们是相同类型,它们就相等,因为它们表示相同的值。

似乎函数 abs 提供了您想要的行为:

int a = -534;
int b = abs(a);
printf("%d, %d", a, b);

请注意,0xfffffdea 中的 f 数量可能会有所不同。例如,在 16 位机器上,它将只是 0xfdea,而在 64 位机器上,您将有 13 个 f,后跟 dea - MSalters
完全正确。我在写答案时想到应该提到 int 大小的一些内容,但我认为这可能会使答案变得混乱,因为这些信息与手头的问题并不特别相关。 - Russ Hayward
2
更重要的是,即使在补码或反码机器上,“dea” 部分也不一定正确。 - Alok Singhal

0

我猜 Tronic 和 Hassan 已经足够回答为什么 b 被打印为-534的第一个情况了。你不应该使用 %d,而应该使用 %u。

至于你的第二个情况,由于存在隐式类型转换,a 和 b 将会相同,导致你的比较没有产生预期的结果。


0
据我所见,if语句失败是因为编译器假设第二个变量应该被视为与第一个相同的类型。尝试使用if(b > a)来查看差异。

这个答案是错误的:在比较有符号整数和无符号整数时,有符号整数被强制转换为无符号整数。因为(a == b),所以(a < b)和(a > b)都是假的。 - Stephen C. Steel
(a < b)和(a > b)都为假,但如果(b > a),则两者都不是。我的编译器也是这样认为的。 - Arthur Kalliokoski

0

关于第二个问题:

比较操作不能在两种不同类型之间进行 - 它们总是隐式转换为“最低公共分母”,在此情况下将是unsigned int。这很恶心和反直觉,我知道。


如果是这种情况,那么强制转换应该将-534转换为零,或者导致运行时错误吗?我在VS2008中编译了代码,a==b为真。 - Hassan Syed
不,将-534转换为“unsigned int”在两种情况下都是允许的(情况#1初始化“b”时和情况#2比较“a”和“b”时)。 - MSalters

0

将有符号整数类型从带符号转换为无符号并不会修改位模式,它只是改变了位模式的解释。

您还存在格式说明符不匹配的问题,应该使用%u来表示无符号整数,但即使这样,结果也不会像您预期的534那样,而是4294966762。

如果您想将负值变为正值,只需取反即可:

unsigned b = (unsigned)-a ;
printf("%d, %u", a, b);

关于第二个例子,涉及不同有符号-无符号类型之间的操作会涉及晦涩的隐式转换规则——请避免。您应该将编译器的警告级别设为高以捕获许多这些错误。例如,在VC ++中建议使用/W4 /WX,在GCC中建议使用-Wall -Werror -Wformat。

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