【问题标题】:Why Do We have unsigned and signed int type in C?为什么我们在 C 中有无符号和有符号的 int 类型?
【发布时间】:2017-05-15 10:10:05
【问题描述】:

我是 C 的初学者。我最近了解了2's Complement 和其他表示负数的方式以及为什么2's complement 是最合适的方式。

我想问的是例如,

int a = -3;
unsigned int b = -3; //This is the interesting Part.

现在,对于int类型的转换

标准说:

6.3.1.3 有符号和无符号整数

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

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

第一段不能用-3不能代表unsigned int

因此第 2 段开始发挥作用,我们需要知道 unsigned int 的最大值。它可以在 limits.h 中以 UINT_MAX 的形式找到。本例中的最大值为4294967295,因此计算为:

-3 + UINT_MAX + 1 = -3 + 4294967295 + 1 = 4294967293  

现在4294967293 二进制是11111111 11111111 11111111 11111101-3 2 的补码形式是11111111 11111111 11111111 11111101 所以它们本质上是相同的位表示,无论我试图分配什么负整数,它总是相同的无符号int.So 不是无符号类型多余的。

现在我知道printf("%d" , b) 根据标准是一种未定义的行为,但这不是一种合理且更直观的做事方式。如果否定表示为2's Complement,则表示将是相同的,这就是我们现在所拥有的,并且使用的其他方式很少见,并且很可能不会在未来的发展中。

所以如果我们只能有一种类型说 int ,现在如果 int x = -1 然后 %d 检查符号位并打印负数如果符号位是 1%ualways 解释纯二进制数字(位)原样。由于使用了2's complement,所以已经处理了加法和减法。那么,这种做事方式不是更直观、更简单吗?

【问题讨论】:

  • 该标准仅描述了 99.9% 的现代 CPU 在将无符号数解释为有符号数时所公开的行为。所以,是的,大多数机器都按照标准所说的那样做。但是有符号和无符号 不是多余的 因此。
  • @tofro 你能详细说明一下吗,我不明白你的意思。
  • 例如为了进行比较,了解数字是否必须被解释为有符号或无符号是至关重要的。

标签: c programming-languages twos-complement unsigned-integer language-concepts


【解决方案1】:

同时拥有输入、输出和计算都很方便。例如,比较和除法分为有符号和无符号两种(顺便说一句,在位级乘法对于无符号和 2 的补码有符号类型是相同的,就像加法和减法一样,两者都可以编译成 CPU 的相同乘法指令)。此外,无符号操作在溢出的情况下不会导致未定义的行为(被零除除外),而有符号操作会。总体而言,无符号算术定义良好,无符号类型只有一种表示形式(与有符号类型的三种不同表示不同,尽管如今实际上只有一种)。

有一个有趣的转折。现代 C/C++ 编译器利用签名溢出导致未定义行为这一事实。逻辑是它永远不会发生,因此可以进行一些额外的优化。如果它真的发生了,标准会说它是未定义的行为,并且你的错误程序在法律上被搞砸了。这意味着您应该避免签名溢出和所有其他形式的 UB。但是,有时您可以仔细编写永远不会导致 UB 的代码,但使用有符号算法比使用无符号算法更有效。

请研究未定义、未指定和实现定义的行为。它们都列在标准末尾的附件之一(J?)中。

【讨论】:

【解决方案2】:

我的回答更抽象,在我看来,在 C 中你不应该关心内存中整数的表示。 C把这个抽象给你,这很好。

将整数声明为unsigned 非常有用。这假设该值永远不会是负数。像浮点数处理实数,signed整数处理...整数和unsigned整数处理自然数。

当您创建负整数会导致未定义行为的算法时。您可以确定您的无符号整数值永远不会是负数。例如,当您迭代数组的索引时。负索引会导致未定义的行为。

另一件事是,当您创建公共 API 时,当您的某个函数需要大小、长度、重量或其他任何对负面没有意义的东西时。这有助于用户理解此值的用途。


另一方面,有些人不同意,因为unsigned 的算术并没有像人们最初期望的那样工作。因为当 unsigned 等于 0 时递减,它将传递到一个非常大的值。有些人期望他等于-1。例如:

// wrong
for (size_t i = n - 1; i >= 0; i--) {
  // important stuff
}

这会产生一个无限循环,如果 n 等于 0 甚至更糟,编译器可能会检测到它,但不是一直检测到:

// wrong
size_t min = 0;
for (size_t i = n - 1; i >= min; i--) {
  // important stuff
}

用无符号整数来做这件事需要一个小技巧:

size_t i = n;
while (i-- > 0) {
  // important stuff
}

在我看来,在语言中拥有unsigned 整数非常重要,如果没有,C 将不完整。

【讨论】:

  • 在您需要与外部世界对话或需要一种快速的方法来做一些语言不直接支持而硬件支持的事情之前,您不会关心类型表示。跨度>
【解决方案3】:

我认为一个主要原因是运营商和运营取决于签名。

如果有符号类型使用 2 的补语,您已经观察到加/减行为对于有符号和无符号类型是相同的(而您一直忽略了这个“如果”有时不是这种情况的事实。)

在许多情况下,编译器需要签名信息来理解程序的目的。

1.整数提升。

当较窄的类型转换为较宽的类型时,编译器将根据操作数的类型生成代码。

例如如果您将signed short 转换为signed int 并且intshort 宽,编译器将生成执行转换的代码,并且该转换不同于“unsigned short”到“signed int”(符号扩展或不是)。

2。算术右移

-1>>1 仍然可以是-1 如果实现选择,但0xffffffffu>>1 必须是0x7fffffffu

3.整数除法

同样,-1/200xffffffffu/20x7fffffffu

4. 32bit 乘以 32bit,得到 64 位结果:

这有点难以解释,所以让我用代码代替。

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void) {
    // your code goes here
    int32_t a=-1;
    int32_t b=-1;
    int64_t c = (int64_t)a * b;
    printf("signed: 0x%016"PRIx64"\n", (uint64_t)c);

    uint32_t d=(uint32_t)-1;
    uint32_t e=(uint32_t)-1;
    uint64_t f = (uint64_t)d * e;
    printf("unsigned: 0x%016"PRIx64"\n", f);

    return 0;
}

演示:http://ideone.com/k30nZ9

5.当然,还有比较。


一个人可以设计一种无符号语言,但是很多操作符需要分成两个或多个版本,以便程序员可以表达程序的目的,例如运算符/需要拆分为udivsdiv,运算符*需要拆分为umulsmul,整数提升需要显式,运算符&gt;需要为@987654342 @/ucmpgt............

那将是一种可怕的语言,不是吗?


奖励:所有指针通常具有相同的位表示,但具有不同的运算符[]-&gt;*++--+-

【讨论】:

    【解决方案4】:

    嗯,最简单和通用的答案是内存维护,C 语言中的每个变量在我们声明它时都会在主内存 (RAM) 中保留一些内存空间,例如: unsigned int var; 将保留2 or 4 字节,范围为0 to 65,5350 to 4,294,967,295

    签名int 的范围为-32,768 to 32,767-2,147,483,648 to 2,147,483,647

    重点是有时您只是不能为负数的正数,例如您的年龄显然不能为负数,因此您将使用“无符号整数”。同样,在处理那些可能包含与signed int 相同范围的负数时,我们将不会使用它。简而言之,一个好的编程习惯是根据我们的需要使用适当的数据类型,这样我们就可以有效地使用计算机内存,并且我们的程序会更加紧凑。

    据我所知,关于 2 的补全是关于特定数据类型或更具体的正确基数。我们根本无法确定它是否是特定数字的 2 补码。但是由于计算机处理二进制,我们仍然有字节数,例如 8 位中 7 的 2 的补码与 32 位和 64 位中的不同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-08-14
      • 2013-12-19
      • 2011-11-25
      • 2010-12-06
      • 2013-06-23
      • 2015-01-28
      • 1970-01-01
      相关资源
      最近更新 更多