【问题标题】:Unsigned and Signed Values in C (Output)C 中的无符号和有符号值(输出)
【发布时间】:2011-04-09 18:49:11
【问题描述】:
signed int x = -5;
unsigned int y = x;

y 的值是多少?这是怎么回事?

【问题讨论】:

  • 当你尝试这个时,你看到了什么?
  • @KennyTM,未定义实现;从 unsigned int 转换为无法表示它的有符号 int 是实现定义的,但反过来 (signed -> unsigned) 是明确定义的。
  • @bdonlan: UINT_MAX 是实现定义。
  • @KennyTM:请修正严重错误的评论。我不敢相信它得到了+3票。有没有管理员能解决这个问题?
  • @Steve Jessop:根据标准,答案是 UINT_MAX - 4。UINT_MAX 是实现定义的。有一个直接的答案,但数字的答案是实现定义的。

标签: c unsigned signed


【解决方案1】:

y=0xfffffffb 它是-5(二进制补码)的二进制表示

【讨论】:

  • 结果不依赖于任何“二进制表示”。结果取决于语言标准的要求。在这种情况下,标准相当明确。
  • @AndreyT。公平地说,虽然没有这样说,但 2 的补码 -> 无符号转换确实会导致相同的位模式(我认为 C++ 标准顺便提到了这一点,不记得 C 标准是否这样做了)。 Philibert 的描述等同于标准中的定义:您可以通过计算带符号值的 2 的补码表示来转换有符号 -> 无符号,然后将其作为无符号值读取。他从未说过实现实际上是这样做的(尽管毫无疑问 2 的补码实现会这样做)。
  • @Steve Jessop:嗯,它不等同于标准中的定义,至少因为标准定义不限于相同大小的类型之间的转换,而“相同表示”的方法是仅在尺寸相同时适用。
  • @AndreyT:是的,它只对被问及的案例等效,根本不涉及其他案例。我更担心int 是 32 位的假设。
【解决方案2】:

这取决于unsigned int 的最大值。通常,unsigned int 是 32 位长,所以 UINT_MAX 是 232 - 1。C 标准(第 6.3.1.3/2 节)要求执行有符号 → 无符号转换

否则,如果新类型是无符号的,则在新类型可以表示的最大值的基础上反复加减一,直到该值在新类型的范围内。

因此 y = x + ((232 - 1) + 1) = 232 - 5 = 4294967291。


在2's complement 平台中,现在大多数实现是,y 也与x 的 2 的补码表示相同。

-5 = ~5 + 1 = 0xFFFFFFFA + 1 = 0xFFFFFFFB = 4294967291。

【讨论】:

    【解决方案3】:

    y的值为UINT_MAX - 5 + 1,即UINT_MAX - 4。

    当您将有符号整数值转换为无符号类型时,该值以 2^N 为模减少,其中 N 是无符号类型中值形成位的数量。这适用于负符号和正符号值。

    如果您从有符号类型转换为相同大小的无符号类型,则上述表示正符号值保持不变(例如,+5 被转换为 5)而负值被添加到 MAX + 1 ,其中MAX 是无符号类型的最大值(-5 被转换为MAX + 1 - 5)。

    【讨论】:

      【解决方案4】:

      来自 C99 标准:

      6.3.1.3 有符号和无符号整数

      1. 当整数类型的值转换为另一种整数类型时 除了 _Bool,如果 该值可以用新类型表示,它是不变的。
      2. 否则,如果新类型是无符号的,则值转换为 反复添加或 比可以表示的最大值减一 在新类型 直到值在新类型的范围内。 49)

      49) 规则描述的是数学值的算术运算,而不是给定类型表达式的值。

      因此,您将有效地查看y = x + UINT_MAX + 1。

      这恰好意味着二进制补码表示形式不变地用作无符号整数,这在大多数现代计算机上非常快,因为它们使用二进制补码表示有符号整数。

      【讨论】:

      • 你少了一个:UINT_MAX 是 2^N-1。
      • 不完全正确。正确的公式是y = x + UINT_MAX - 1。
      • @AndreyT:其实正确的公式是y = x + UINT_MAX + 1。
      • @Jens @KennyTM,确实,已修复
      【解决方案5】:

      签名值通常存储为two's complement:

      二进制补码是一种将负数编码为普通二进制的方法,这样加法仍然有效。加 -1 + 1 应该等于 0,但普通加法会给出 2 或 -2 的结果,除非该操作特别注意符号位并改为执行减法。无需这个额外步骤,二进制补码就能得到正确的和。

      这意味着数字 -5 和 4294967291 在内存中的实际表示(对于 32 位字)是相同的,例如:0xFFFFFFFB 或 0b11111111111111111111111111111011。所以当你这样做时:

      unsigned int y = x;
      

      x 的内容被逐字复制,即按位复制到y。这意味着如果您检查内存中x 和y 的原始值,它们将是相同的。但是,如果你这样做:

      unsigned long long y1 = x;
      

      x 的值将在转换为无符号长整型之前进行符号扩展。在 long long 为 64 位的常见情况下,这意味着 y1 等于 0xFFFFFFFFFFFFFFFB。

      重要的是要注意转换为更大的类型时会发生什么。转换为更大有符号值的有符号值将被符号扩展。如果源值是无符号的,则不会发生这种情况,例如:

      unsigned int z = y + 5;
      long long z1 = (long long)x + 5; // sign extended since x is signed
      long long z2 = (long long)y + 5; // not sign extended since y is unsigned
      

      z 和 z1 将等于 0,但 z2 不会。这可以通过在扩展它之前将值强制转换为已签名 来解决:

      long long z3 = (long long)(signed int)y + 5;
      

      或者如果您不希望出现符号扩展,则以此类推:

      long long z4 = (long long)(unsigned int)x;
      

      【讨论】:

      • 但问题是关于转换为 unsigned 类型。然而,在答案中,您只提到了对 signed 类型的转换。
      • 我不敢苟同,尽管我确实将我的回应集中在转换为有符号类型上,因为那里有龙。我也不认为我的回答值得反对,但我有偏见。
      • +1:表示“二进制补码”,即使实际上 C99 的回避措辞给出的定义也适用于使用 BCD 的编译器。我想知道这样的 C 编译器是否存在,直到被证明是错误的,我相信所有当前存在的 C 编译器都使用二进制补码。
      猜你喜欢
      • 2011-10-11
      • 2012-09-30
      • 1970-01-01
      • 2015-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-04-05
      相关资源
      最近更新 更多