如何在C语言中将无符号整型转换为有符号整型?

29

如果我的问题看起来有些奇怪,那我向你道歉。我正在调试我的代码,这似乎是问题所在,但我不确定。

谢谢!


1
真正的问题是,如果无符号整数中的值超出了有符号整数可以表示的范围,您想要做什么。如果在范围内,只需分配即可完成。如果超出范围,那将产生未指定的结果,因此您可能需要先将其减少到正确的范围,或将其分配给较大的有符号类型。 - Jerry Coffin
不是未指定的,而是实现定义的(C99 6.3.1.3§3)。否则我同意,并将其分配给一个更大的有符号整数是最简单的解决方案。 - Gauthier
7个回答

35

这取决于您想要的行为是什么。一个 int 无法容纳许多 unsigned int 可以容纳的值。

您可以像平常一样进行强制类型转换:

int signedInt = (int) myUnsigned;

如果unsigned的值超过了int所能容纳的最大值,这会导致问题。这意味着有一半可能的unsigned值会导致错误行为,除非你特别注意它。

如果你不得不进行转换却没有好的理由,那么你应该重新审视一下如何存储值。

编辑:如评论中所述,最大值取决于平台。但是你可以通过使用 INT_MAXUINT_MAX 来访问它。

对于通常的4字节类型:

4 bytes = (4*8) bits = 32 bits
如果使用了所有32位,如同使用unsigned,最大值将是2^32 - 1或4,294,967,295
有符号的int为了表示正负数牺牲了1位,所以最大值为2^31 - 1或2,147,483,647。注意这是另一个值的一半。

你知道无符号整数的最大值是多少吗?int类型的呢? - Eric Brotto
1
@Eric Brotto: 这将取决于您正在编译的系统。通常,如果您需要检查,可以使用INT_MAX、UINT_MAX宏。在大多数32位系统上,INT_MAX将为(2^31)-1,UINT_MAX将为(2^32)-1。请注意,将UINT_MAX强制转换为int将为-1。 - ProdigySim
2
通常情况下,这是指C标准要求它们(对于托管系统)。 - Jim Balter
实际上,即使在独立系统(即非托管系统)中,这些宏(位于<limits.h>中)也是必需的。 - DevSolar

7

在我看来,这个问题是一个常见的问题。正如各种答案中所述,将不在范围[0,INT_MAX]内的无符号值分配给变量是实现定义的,甚至可能会引发信号。如果将无符号值视为有符号数字的二进制补码表示,则可能最便携的方法是以下代码片段所示的方式:

#include <limits.h>
unsigned int u;
int i;

if (u <= (unsigned int)INT_MAX)
  i = (int)u; /*(1)*/
else if (u >= (unsigned int)INT_MIN)
  i = -(int)~u - 1; /*(2)*/
else
  i = INT_MIN; /*(3)*/
  • 第一条分支是显而易见的,不会引发任何溢出或陷阱,因为它保留了值。

  • 第二条分支为避免有符号整数溢出而采取了一些措施,它通过按位非运算对值进行一次补码,将其转换为“int”(现在无法溢出),然后取反该值并减去1,这里也不会溢出。

  • 第三条分支提供了我们必须接受的毒药,即针对补码或符号/大小目标的一次补码,因为有符号整数表示范围小于二进制补码表示范围。

这可能会归结为在二进制补码目标上的简单移动;至少我观察到GCC和CLANG也是这样。如果想将执行限制为二进制补码目标,则可以将代码压缩为:

#include <limits.h>
unsigned int u;
int i;

if (u <= (unsigned int)INT_MAX)
  i = (int)u; /*(1)*/
else
  i = -(int)~u - 1; /*(2)*/

该配方适用于任何有符号/无符号类型对,并且最好将代码放入宏或内联函数中,以便编译器/优化器可以解决它。(在这种情况下,使用条件运算符重写该配方是有帮助的。但它不太易读,因此不是解释该策略的好方法。)
是的,一些到 'unsigned int' 的强制转换是多余的,但它们可能有助于非专业读者,某些编译器会发出有关有符号/无符号比较的警告,因为隐式转换会导致一些不直观的语言设计行为。

你确定(unsigned int)INT_MIN是被良好定义的吗? - Dan Bechard
2
是的。只要编译器头文件以应该的方式定义INT_MIN,即带有最负可能值的有符号整数,从负有符号整数到无符号整数的转换就是明确定义的,并且始终产生二进制补码的位模式。 - Pearly

6

1
我认为你在上面的表达式中想要说的是"z = (unsigned int)y;"。由于y已经是一个int,将其强制转换为int有点毫无意义... - A. Levy

3

如果您有一个变量unsigned int x;,您可以使用(int)x将其转换为int


1

就是这么简单:

unsigned int foo;
int bar = 10;

foo = (unsigned int)bar;

或者反过来...


3
强制类型转换通常是为了掩盖不正确的代码,使其更加冗余和难看。在 C 语言中,赋值操作本质上包括对于那些有意义的类型进行转换(甚至包括一些没有意义的类型)。 - R.. GitHub STOP HELPING ICE
2
这并不是真的,如果你正在检查是否经过了一定的时间,那么这实际上是一个相当常见的问题。你的间隔应该是一个无符号整数,因为它没有被签名是没有意义的,但是time_t总是被签名的,所以它可以表示1970年之前的日期。如果在执行if ((now - then) > interval)时不进行转换,编译器将会生成警告。 - Arran Cudbard-Bell

1
如果在同一表达式中使用无符号整数和(有符号)整数,则有符号整数会被隐式转换为无符号整数。这是 C 语言的一个相当危险的特性,因此您需要注意。它可能是您的错误的原因,也可能不是。如果您想要更详细的答案,您需要发布一些代码。

0

来自C++Primer第五版第35页的一些解释

如果我们将一个超出无符号类型对象范围的值赋给它,结果是该值模目标类型可以容纳的值的数量的余数。

例如,一个8位无符号字符可以容纳从0到255(包括)的值。如果我们分配一个超出此范围的值,则编译器会分配该值模256的余数。

unsigned char c = -1; // assuming 8-bit chars, c has value 255

如果我们将一个超出范围的值赋给有符号类型的对象,结果是未定义的。程序可能会正常工作,也可能会崩溃,或者产生垃圾值。

第160页: 如果任何操作数是无符号类型,则操作数转换的类型取决于机器上整数类型的相对大小。

... 当有符号性不同时,且无符号操作数的类型与有符号操作数的类型相同或更大时,有符号操作数将被转换为无符号操作数。

剩下的情况是有符号操作数比无符号操作数具有更大的类型。在这种情况下,结果取决于机器。如果无符号类型中的所有值都适合大型类型,则无符号操作数将被转换为有符号类型。如果值不适合,则有符号操作数将被转换为无符号类型。

例如,如果操作数是长整型和无符号整型,并且int和long具有相同的大小,则长度将被转换为无符号整型。如果long类型具有更多位,则无符号整型将被转换为long。

我发现阅读这本书非常有帮助。


如果我们将一个超出有符号类型对象范围的值赋给它,结果是未定义的 - 这是错误的,实际上它是实现定义的。 - M.M
此外,这是一个C语言问题,但您引用了一个C++的参考文献。C和C++是不同的编程语言。 - M.M

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