【问题标题】:What is the difference between literals and variables in C (signed vs unsigned short ints)?C中的文字和变量(有符号与无符号短整数)有什么区别?
【发布时间】:2015-10-26 06:47:15
【问题描述】:

我在Computer Systems: A Programmer's Perspective, 2/E一书中看到了以下代码。这很好用并创建了所需的输出。输出可以通过有符号和无符号表示的差异来解释。

#include<stdio.h>
int main() {
    if (-1 < 0u) {
        printf("-1 < 0u\n");
    }
    else {
        printf("-1 >= 0u\n");
    }
    return 0;
}

上面的代码产生-1 &gt;= 0u,但是下面的代码应该和上面的一样,没有!换句话说,

#include <stdio.h>

int main() {

    unsigned short u = 0u;
    short x = -1;
    if (x < u)
        printf("-1 < 0u\n");
    else
        printf("-1 >= 0u\n");
    return 0;
}

产生-1 &lt; 0u。为什么会这样?我无法解释。

请注意,我见过类似的问题,例如 this,但它们没有帮助。

PS。正如@Abhineet 所说,可以通过将short 更改为int 来解决困境。然而,如何解释这种现象呢?换句话说,4 个字节中的-10xff ff ff ff,2 个字节中是0xff ff。给定它们作为 2s-complement 被解释为unsigned,它们具有429496729565535 的对应值。它们都不少于0,我认为在这两种情况下,输出都需要为-1 &gt;= 0u,即x &gt;= u

在小端英特尔系统上的示例输出:

简称:

-1 < 0u
u =
 00 00
x =
 ff ff

对于整数:

-1 >= 0u
u =
 00 00 00 00
x =
 ff ff ff ff

【问题讨论】:

  • C 根据而不是表示来表现。所有关于 2 的补码和 ffff 和 65535 等的东西都是无关紧要的。
  • 不要对非代码文本使用代码格式。

标签: c bit-manipulation twos-complement unsigned-integer integer-promotion


【解决方案1】:

上面的代码产生 -1 >= 0u

所有整数文字(数字常量)都有一个类型,因此也有一个符号。默认情况下,它们的类型是 int,它是已签名的。当您附加u 后缀时,您会将文字转换为unsigned int

对于任何有一个有符号操作数和一个无符号操作数的 C 表达式,平衡规则(正式名称:the usual arithmetic conversions)会隐式地将有符号类型转换为无符号类型。

从有符号到无符号的转换是明确定义的(6.3.1.3):

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

例如,对于标准二进制补码系统上的 32 位整数,无符号整数的最大值为 2^32 - 1(4294967295,limits.h 中的 UINT_MAX)。比最大值大一是2^32。和-1 + 2^32 = 4294967295,所以文字-1 被转换为一个值为4294967295 的无符号整数。大于 0。


但是,当您将类型切换为短类型时,您最终会得到一个小整数类型。这是两个示例之间的区别。每当一个小整数类型是表达式的一部分时,整数提升规则会隐式地将其转换为更大的 int (6.3.1.1):

如果一个 int 可以表示原始类型的所有值(受限制 通过宽度,对于一个位域),该值被转换为一个 int; 否则,它将转换为无符号整数。这些被称为 整数促销。所有其他类型都不变整数 促销活动。

如果short 在给定平台上小于int(32 位和64 位系统上的情况),任何shortunsigned short 将因此总是被转换为int,因为它们可以放在一个里面。

因此,对于表达式 if (x &lt; u),您实际上最终会得到 if((int)x &lt; (int)u),它的行为与预期一样(-1 小于 0)。

【讨论】:

  • 谢谢。这就解释了这个案子。但是,我想知道为什么设计师会这样决定?你有什么想法吗?
  • @AliShakiba:当您遇到不同的操作数时,您可以决定将两个操作数转换为signed,或者将两个操作数转换为unsigned,然后再进行比较。由于signed int 是“默认”类型,因此对其中一个操作数使用unsigned 文字意味着您有理由指定此附加限定符。而且由于 C 被设计为“接近硬件”,因此很自然地尝试将所有内容都放入平台的本机字长中,以便能够使用适当的指令(这些指令不会“混合”操作数类型并且通常对字进行操作)大小操作数)。
  • @AliShakiba 整数提升背后的基本原理是这样的:例如,如果您有char x = 200, char y=200;,然后执行x + y,则表达式不会溢出。不过,整数提升是 C 语言中的一种类型不一致,多年来它造成的弊大于利,因为隐式隐式提升错误比简单的整数溢出错误更难找到。此外,隐式类型提升规则有些复杂,因此有很多 C 程序员不知道它们是如何工作的,这很不幸。
  • @Lundin:最初的理由还指出,大多数实现会将短无符号类型的操作提升为有符号的,从提升到无符号的方式无法区分,即使结果在范围 INT_MAX+1uUINT_MAX,除非结果以某种方式使用,并且几乎可以肯定地影响了将事情推广为签名的决定,因为签名推广通常在差异的情况下是正确的在大多数现有实现中都会很重要,但未签名的提升是正确的......
  • ...在标准没有要求但现有实现做了正确的事情的情况下。我怀疑标准的作者是否会像他们那样编写规则,如果他们期望当产品在@987654346 范围内时,针对静默环绕平台的编译器有时会以奇怪的方式处理像uint1 = ushort1*ushort2; 这样的代码会变得流行@ 到 UINT_MAX.
【解决方案2】:

您遇到了 C 的整数提升规则。

类型小于int 的运算符会自动将其操作数提升为intunsigned int。有关更详细的说明,请参阅 cmets。如果之后类型仍然不匹配(例如 unsigned int 与 int),则二进制(双操作数)运算符还有一个步骤。我不会尝试更详细地总结规则。 请参阅 Lundin 的回答

This blog post 更详细地介绍了这一点,并提供了一个与您类似的示例:signed 和 unsigned char。它引用了 C99 规范:

如果一个 int 可以表示原始类型的所有值,则该值为 转换为 int;否则,它将转换为无符号整数。 这些被称为整数促销。所有其他类型不变 通过整数促销。


您可以在 Godbolt with a function that returns one or zero 之类的工具上更轻松地使用它。只需查看编译器输出,看看最终会发生什么。

#define mytype short

int main() {
    unsigned mytype u = 0u;
    mytype x = -1;
    return (x < u);
}

【讨论】:

  • 这很好。但是,正如问题末尾的示例所示,shortunsigned short 都是两个字节,但有不同的解释。感谢您的链接。
  • @AliShakiba:整数提升的规则可能不直观。 使用短变量(都被提升为 int)与 int 变量(int 不能代表所有可能的无符号整数)得到不同结果的原因。
  • 这里实际上有两套规则:“整数提升”将shortunsigned short 提升为int(在这个平台上),“通常的算术转换”提升@ 987654332@ 到 unsignedunsigned 进行比较时。大多数运算符将执行“整数提升”,然后执行“通常的算术转换”。值得注意的例外是位移运算符,它只进行整数提升。
  • 这很好。 C 中unsigned 的扩展规则是用0s 填充左边的额外新位,signed 类型用msb 填充。因此,有两种情况:(1)如果先扩展,然后强制转换为无符号,最后比较,则0xff ff扩展为0xff ff ff ff,然后与0比较为无符号,导致-1 &gt;= 0u。 (2) 如果我们先转换为无符号,然后扩展,0xff ff 扩展为0x 00 00 ff ff,最后与0u 比较,我认为应该评估为-1 &gt;= 0u。在这两种情况下,它都必须是-1 &gt;= 0u!我完全糊涂了!
  • 您的回答表明参数被提升是因为&gt; 被赋予了不同的类型。然而这是错误的。如果参数是两个短裤,那么两者都被提升为 int 。 整数提升首先出现在&gt; 和大多数其他二元运算符中:小于int 的类型被提升为int。只有这样,如果类型仍然不同,则需要进一步转换。
【解决方案3】:

除了您似乎假设的以外,这不是类型的特定宽度的属性,这里是 2 字节与 4 字节,而是要应用的规则的问题。整数提升规则规定 shortunsigned short 在对应的值范围适合 int 的所有平台上转换为 int。由于这里是这种情况,因此两个 都被保留并获得类型 int-1int 中完全可以表示,0 也是如此。所以-1中的测试结果小于0

在针对0u 测试-1 的情况下,公共转换选择unsigned 类型作为两者都转换为的公共类型。 -1转换成unsigned就是UINT_MAX的值,大于0u

这是一个很好的例子,为什么你不应该使用“窄”类型来进行算术或比较。仅当您有服务器大小限制时才使用它们。简单变量很少会出现这种情况,但主要是对于大型数组,您可以从存储在窄类型中真正受益。

【讨论】:

  • 在数组中以窄类型存储数据非常棒。但是,将数组中的值加载到狭窄的局部变量中通常不是很好。将数组值加载到int 局部变量中,无需担心整数提升规则;只是有符号与无符号 int 的通常规则。 x86 至少具有有效的指令,可以在从内存加载到寄存器时动态地对 int8_t 或 int16_t 进行符号扩展。关于 ARM 或其他重要架构的 IDK。
【解决方案4】:

0u 不是unsigned short,而是unsigned int

Edit:: 对行为的解释, How comparison is performed ?

正如 Jens Gustedt 所回答的,

这被标准称为“通常的算术转换”,并且 每当两个不同的整数类型作为操作数出现时都适用 同一个运算符。

本质上是什么

如果类型有不同的宽度(更准确地说是标准 调用转换排名)然后如果两者都转换为更广泛的类型 类型具有相同的宽度,除了非常奇怪的架构之外, 其中无符号获胜 有符号到无符号值的转换 -1 任何类型总是产生最高的可表示值 无符号类型。

他写的更多解释性博客可以找到here

【讨论】:

  • 感谢@Abhineet。这是正确的。但是,我很好奇为什么会这样? -1 在 4 个字节中是 0xff ff ff ff,在 2 个字节中是 0xff ff。给定它们作为 2s-complement 被解释为unsigned,它们具有429496729565535 的对应值。它们都不少于0,我认为在这两种情况下,输出都需要为-1 &gt;= 0u,即x &gt;= u
  • 这没有回答问题,也没有解释为什么int 变量给出的结果与short 变量不同。
猜你喜欢
  • 2010-10-11
  • 2011-08-10
  • 1970-01-01
  • 2017-06-07
  • 2011-04-05
  • 2017-07-25
  • 2016-02-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多