为什么我们在C中有无符号和有符号的int类型?

时间:2016-12-31 10:38:49

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

我是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以外的另一个整数类型时,if   该值可以用新类型表示,不变。

     

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

第一段不能用作-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的补丁格式为11111111 11111111 11111111 11111101,因此它们基本上是相同的位表示,它总是相同的无论我试图分配给无符号整数的负整数。所以不是无符号类型的冗余。

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

因此,如果我们只有一个类型说int,那么现在如果int x = -1%d检查符号位,如果符号位为1%u则打印负数总是按原样解释普通二进制数字(位)。由于使用2's complement,已经处理了加法和减法。所以,这不是一种更直观,更简单的做事方式。

4 个答案:

答案 0 :(得分:7)

输入,输出和计算都很方便。例如,比较和除法有符号和无符号变体(顺便说一句,在无符号和2的补码有符号类型的位级乘法是相同的,就像加法和减法一样,两者都可以编译成相同的乘法指令CPU)。此外,无符号运算在溢出的情况下不会导致未定义的行为(除零除外),而签名操作则不会。总体而言,无符号算术已明确定义,无符号类型具有单一表示形式(与签名类型的三种不同类型不同,尽管如今在实践中只有一种)。

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

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

答案 1 :(得分:3)

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

声明一个整数,因为unsigned非常有用。这假设价值永远不会是负面的。与浮点数处理实数一样,signed整数句柄...整数和unsigned整数处理自然数。

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

另一个原因是当你创建一个公共API时,你的一个函数需要一个大小,一个长度,一个权重或者其他任何不利的东西。这有助于用户理解此值的用途。

另一方面,有些人不同意,因为unsigned的算法不像人们首先预期的那样有用。因为当unsigned在等于​​零时递减,它将传递给一个非常大的值。有些人希望他等于-1。例如:

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

如果n等于零,会产生无限循环甚至更糟,编译器可能会检测到它但不是所有时间:

// 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就不会完整。

答案 2 :(得分:2)

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

您已经观察到,对于有符号和无符号类型,加/减行为相同,如果有符号类型使用2的恭维(并且您忽略了这个“如果”有时不是这样的事实。)

在许多情况下,编译器需要签名信息才能理解程序的用途。

<强> 1。整数推广。

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

E.g。如果您将signed short转换为signed intint宽于short,则编译器会生成执行转换的代码,并且该转换与“unsigned short”不同“signed int”(签名是否延长)。

<强> 2。算术右移

如果实施选择,

-1>>1仍可-1,但0xffffffffu>>1必须为0x7fffffffu

第3。整数划分

同样,-1/200xffffffffu/20x7fffffffu

<强> 4。 32位乘以32位,结果为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,整数提升需要明确,运营商>需要scmpgt / ucmpgt .........

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

奖励:所有指针通常具有相同的位表示,但具有不同的运算符[]->*++--,{{1 },+

答案 3 :(得分:0)

最简单和最通用的答案是内存维护,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

关键是有时你只是正数而不是负数,例如你的年龄显然它不能是负数所以你会使用'unsigned int'。同样,在处理数字时,可以包含与signed int相同范围的负数,我们将使用它。简而言之,一个好的编程实践是根据我们的需要使用适当的数据类型,这样我们就可以有效地使用计算机内存,并且我们的程序将更加紧凑。

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