5

我是 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-32 的补码形式是11111111 11111111 11111111 11111101,所以它们本质上是相同的位表示,无论我试图分配什么负整数给 unsigned int,它总是相同的。所以 unsigned 类型不是多余的。

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

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

4

4 回答 4

7

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

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

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

于 2016-12-31T12:58:24.623 回答
3

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

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

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

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


另一方面,有些人不同意,因为 的算术unsigned并不像人们最初期望的那样工作。因为当 anunsigned在等于 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 将是不完整的。

于 2016-12-31T13:44:04.593 回答
2

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

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

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

1.整数提升。

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

例如,如果您转换signed shortsigned intandint比 更宽short,编译器将生成执行转换的代码,并且该转换不同于“unsigned short”到“signed int”(符号扩展与否)。

2.算术右移

-1>>1-1如果实施选择,可以是静止的,但0xffffffffu>>1必须是0x7fffffffu

3.整数除法

同样,-1/200xffffffffu/20x7fffffffu

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

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

#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. 当然,比较。


可以设计一种无符号的语言,但是很多运算符需要拆分成两个或多个版本,以便程序员可以表达程序的目的,例如运算符/需要拆分为udivand sdiv,运算符*需要是拆分为umuland smul,整数提升需要显式,操作符>需要是scmpgt/ ucmpgt…………

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


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

于 2016-12-31T16:39:56.217 回答
0

那么最简单和通用的答案是内存维护,C 语言中的每个变量在我们声明它时都会在主内存(RAM)中保留一些内存空间,例如: unsigned int var; 将保留2 or 4字节并且范围为0 to 65,535or 0 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 位。

于 2016-12-31T17:34:39.747 回答