C中的有符号到无符号转换 - 它总是安全的吗?

Posted

技术标签:

【中文标题】C中的有符号到无符号转换 - 它总是安全的吗?【英文标题】:Signed to unsigned conversion in C - is it always safe? 【发布时间】:2010-09-08 05:08:13 【问题描述】:

假设我有以下 C 代码。

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

这里发生了哪些隐式转换,这段代码对于ui 的所有值是否安全? (安全,即使此示例中的 result 会溢出到某个巨大的正数,我也可以将其转换回 int 并获得真正的结果。)

【问题讨论】:

【参考方案1】:

简答

您的i 将通过添加UINT_MAX + 1转换为无符号整数,然后将使用无符号值进行加法运算,从而得到较大的result(取决于ui 的值)。

长答案

根据 C99 标准:

6.3.1.8 常用算术转换

    如果两个操作数的类型相同,则无需进一步转换。 否则,如果两个操作数都具有有符号整数类型或都具有无符号整数类型,则具有较小整数转换等级的类型的操作数将转换为具有较高等级的操作数的类型。 否则,如果无符号整数类型的操作数的等级大于或等于另一个操作数类型的等级,则将有符号整数类型的操作数转换为无符号整数类型的操作数的类型。 否则,如果带符号整数类型的操作数的类型可以表示无符号整数类型的操作数类型的所有值,则将无符号整数类型的操作数转换为带符号整数类型的操作数的类型输入。 否则,两个操作数都将转换为与带符号整数类型的操作数的类型相对应的无符号整数类型。

在您的情况下,我们有一个无符号整数 (u) 和有符号整数 (i)。参考上面的 (3),由于两个操作数具有相同的等级,您的 i 需要转换为无符号整数。

6.3.1.3 有符号和无符号整数

    当整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新类型表示,则保持不变。 否则,如果新类型是无符号的,则在新类型可以表示的最大值的基础上反复加减一,直到该值在新类型的范围内。 否则,新类型是有符号的,值不能在其中表示;结果是实现定义的,或者引发了实现定义的信号。

现在我们需要参考上面的(2)。您的i 将通过添加UINT_MAX + 1 转换为无符号值。所以结果将取决于您的实现如何定义UINT_MAX。会很大,但不会溢出,因为:

6.2.5 (9)

涉及无符号操作数的计算永远不会溢出,因为无法由生成的无符号整数类型表示的结果会以比结果类型可以表示的最大值大一的数字为模减少。

奖金:算术转换半 WTF

#include <stdio.h>

int main(void)

  unsigned int plus_one = 1;
  int minus_one = -1;

  if(plus_one < minus_one)
    printf("1 < -1");
  else
    printf("boring");

  return 0;

您可以使用此链接在线尝试:https://repl.it/repls/QuickWhimsicalBytes

奖励:算术转换副作用

算术转换规则可以通过将一个无符号值初始化为-1来得到UINT_MAX的值,即:

unsigned int umax = -1; // umax set to UINT_MAX

由于上述转换规则,无论系统的带符号数表示如何,都保证可移植。有关更多信息,请参阅此 SO 问题:Is it safe to use -1 to set all bits to true?

【讨论】:

我不明白为什么它不能简单地做一个绝对值然后将其视为无符号,就像正数一样? @D.Singh 您能否指出答案中的错误部分? 为了将有符号转换为无符号,我们添加无符号值的最大值(UINT_MAX +1)。同样,从无符号转换为有符号的简单方法是什么?我们是否需要从最大值中减去给定的数字(在 unsigned char 的情况下为 256)?例如:140 转换为有符号数时变为-116。但是 20 本身就变成了 20。那么这里有什么简单的技巧吗? @JonWheelock 见:***.com/questions/8317295/…【参考方案2】:

从有符号到无符号的转换不一定只是复制或重新解释有符号值的表示。引用 C 标准(C99 6.3.1.3):

当整数类型的值被转换为_Bool以外的其他整数类型时,如果 该值可以用新类型表示,它是不变的。

否则,如果新类型是无符号的,则通过重复添加或转换值 比新类型可以表示的最大值减一 直到值在新类型的范围内。

否则,新类型是有符号的,值不能在其中表示;无论是 结果是实现定义的或引发了实现定义的信号。

对于如今几乎普遍的二进制补码表示,规则确实对应于重新解释位。但是对于其他表示(符号和大小或一个的补码),C 实现仍然必须安排相同的结果,这意味着转换不能只复制位。例如,(unsigned)-1 == UINT_MAX,无论表示形式如何。

通常,C 中的转换被定义为对值进行操作,而不是对表示进行操作。

回答原来的问题:

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

i 的值被转换为无符号整数,产生UINT_MAX + 1 - 5678。然后将该值与无符号值 1234 相加,得到UINT_MAX + 1 - 4444

(与无符号溢出不同,有符号溢出会调用未定义的行为。环绕很常见,但不受 C 标准的保证——编译器优化可能会对做出无根据假设的代码造成严重破坏。)

【讨论】:

【参考方案3】:

参考The C Programming Language, Second Edition(ISBN 0131103628),

您的加法操作导致 int 转换为无符号 int。 假设二进制补码表示和相同大小的类型,位模式不会改变。 从无符号整数到有符号整数的转换取决于实现。 (但它可能会按照您现在在大多数平台上所期望的方式工作。) 在组合不同大小的有符号和无符号的情况下,规则会稍微复杂一些。

【讨论】:

【参考方案4】:

当一个无符号变量和一个有符号变量相加(或任何二元运算)时,两者都被隐式转换为无符号,在这种情况下会产生巨大的结果。

所以从某种意义上说,结果可能是巨大的和错误的,但它永远不会崩溃,这是安全的。

【讨论】:

不正确。 6.3.1.8 通常的算术转换 如果将一个 int 和一个 unsigned char 相加,则后者将转换为 int。如果将两个 unsigned char 相加,它们将转换为 int。【参考方案5】:

当从有符号转换为无符号时,有两种可能性。最初为正的数字保持(或被解释为)相同的值。最初为负数的数字现在将被解释为更大的正数。

【讨论】:

【参考方案6】:

如前所述,您可以毫无问题地在有符号和无符号之间来回转换。有符号整数的边界情况是 -1 (0xFFFFFFFF)。尝试从中加减,你会发现你可以回退并让它正确。

但是,如果您要来回转换,我强烈建议您命名变量,以便清楚它们是什么类型,例如:

int iValue, iResult;
unsigned int uValue, uResult;

如果在没有提示的情况下命名变量,很容易被更重要的问题分散注意力并忘记哪个变量是什么类型。您不想强制转换为无符号然后将其用作数组索引。

【讨论】:

【参考方案7】:

这里发生了什么隐式转换,

i 将被转换为无符号整数。

这段代码对于 u 和 i 的所有值是否安全?

在定义明确的意义上是安全的(参见https://***.com/a/50632/5083516)。

这些规则通常以难以阅读的标准语言编写,但基本上无论有符号整数中使用什么表示,无符号整数都将包含数字的 2 的补码表示。

加法,减法和乘法将在这些数字上正常工作,从而产生另一个无符号整数,其中包含一个表示“实际结果”的二进制补码。

除法和转换为更大的无符号整数类型将得到明确定义的结果,但这些结果不会是“实际结果”的 2 的补码表示。

(安全,即使此示例中的结果会溢出到某个巨大的正数,我也可以将其转换回 int 并获得真正的结果。)

虽然从有符号到无符号的转换是由标准定义的,但反向是由实现定义的,gcc 和 msvc 都定义了转换,以便在将存储在无符号整数中的 2 的补数转换回时获得“真实结果”有符号整数。我希望您只会在不使用 2 的补码作为有符号整数的晦涩系统上发现任何其他行为。

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx

【讨论】:

【参考方案8】:

可怕的答案很多

Ozgur Ozcitak

当您从已签名转换为未签名时 (反之亦然)内部 数字的表示不 改变。改变的是如何 编译器解释符号位。

这是完全错误的。

马茨·弗雷德里克森

当一个未签名和一个签名时 添加变量(或任何二进制 操作)两者都是隐式的 转换为无符号,这将在 这个案子的结果是巨大的。

这也是错误的。如果由于无符号类型中的填充位而具有相同的精度,则无符号整数可以提升为整数。

smh

您的加法操作导致 int 转换为无符号整数。

错了。也许有,也许没有。

从 unsigned int 转换为有符号 int 依赖于实现。 (但 它可能按您期望的方式工作 如今在大多数平台上。)

错了。如果它导致溢出或者值被保留,它要么是未定义的行为。

匿名

i 的值被转换为 无符号整数 ...

错了。取决于 int 相对于 unsigned int 的精度。

泰勒价格

如前所述,您可以 在签名和之间来回投射 未签名没有问题。

错了。尝试存储有符号整数范围之外的值会导致未定义的行为。

现在我终于可以回答这个问题了。

如果 int 的精度等于 unsigned int,则 u 将被提升为有符号 int,您将从表达式 (u+i) 中得到值 -4444。现在,如果 u 和 i 有其他值,您可能会得到溢出和未定义的行为,但使用这些确切的数字,您将得到 -4444 [1]。该值将具有 int 类型。但是您正在尝试将该值存储到 unsigned int 中,然后将其转换为 unsigned int 并且结果最终将具有的值将是 (UINT_MAX+1) - 4444。

如果 unsigned int 的精度大于 int 的精度,signed int 将被提升为 unsigned int 产生的值 (UINT_MAX+1) - 5678 将被添加到另一个 unsigned int 1234。如果你而且我还有其他值,这使得表达式超出范围 0..UINT_MAX 值(UINT_MAX+1)将被添加或减去,直到结果确实落在范围 0..UINT_MAX)并且没有未定义行为会发生。

什么是精度?

整数具有填充位、符号位和值位。无符号整数显然没有符号位。 Unsigned char 进一步保证没有填充位。一个整数的值位数就是它的精度。

[陷阱]

如果存在填充位,则不能单独使用宏大小来确定整数的精度。并且字节的大小不必是 C99 定义的八位字节(八位)。

[1] 溢出可能发生在两个点之一。在添加之前(在提升期间) - 当您有一个 unsigned int 太大而无法放入 int 时。即使unsigned int在一个int的范围内,加法后也可能发生溢出,加法后结果仍有可能溢出。

【讨论】:

“无符号整数可以提升为整数”。不对。由于类型已经是 rank >= int,因此不会发生整数 promotion。 6.3.1.1:“任何无符号整数类型的等级应等于相应有符号整数类型的等级,如果有的话。”和 6.3.1.8:“否则,如果具有无符号整数类型的操作数的等级大于或等于另一个操作数类型的等级,则具有符号整数类型的操作数将转换为无符号整数类型的操作数的类型。"两者都保证int 在应用通常的算术转换时转换为unsigned int 6.3.1.8 仅在整数提升后发生。开头段落说“否则,对两个操作数都执行整数提升。然后将以下规则应用于提升的操作数”。所以去阅读促销规则 6.3.1.1 ...“一个整数类型的对象或表达式,其整数转换等级小于或等于 int 和 unsigned int 的等级”和“如果一个 int 可以表示原始类型,值转换为 int"。 6.3.1.1 整数提升用于将一些不是intunsigned int 的整数类型转换为需要unsigned intint 类型的类型之一。在 TC2 中添加了“或等于”以允许将等于 intunsigned int 的转换等级的枚举类型转换为其中一种类型。所描述的促销从未打算在unsigned intint 之间转换。 unsigned intint 之间的通用类型确定仍然受 6.3.1.8 的约束,即使在 TC2 之后也是如此。 在批评别人的错误答案的同时发布错误答案听起来不是一个好的工作策略...... ;-) 我不会投票删除,因为这种程度的错误加上傲慢太有趣了

以上是关于C中的有符号到无符号转换 - 它总是安全的吗?的主要内容,如果未能解决你的问题,请参考以下文章

一元减号和有符号到无符号的转换

C ++十六进制字符串到无符号整数[重复]

汇编中的有符号-无符号-溢出-进位

c ++流负数转换

从有符号/无符号字符到无符号/有符号整数类型转换的 IA32 汇编代码

请问FPGA中Dec表示是有符号还是无符号型啊