我是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
,已经处理了加法和减法。所以,这不是一种更直观,更简单的做事方式。
答案 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 int
且int
宽于short
,则编译器会生成执行转换的代码,并且该转换与“unsigned short”不同“signed int”(签名是否延长)。
<强> 2。算术右移
如果实施选择, -1>>1
仍可-1
,但0xffffffffu>>1
必须为0x7fffffffu
第3。整数划分
同样,-1/2
为0
,0xffffffffu/2
为0x7fffffffu
<强> 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;
}
<强> 5。当然还有比较。
可以设计一种无签名语言,但是很多运营商需要分成两个或更多版本,以便程序员可以表达程序的目的,例如:运算符/
需要分为udiv
和sdiv
,运算符*
需要分为umul
和smul
,整数提升需要明确,运营商>
需要scmpgt
/ ucmpgt
.........
那将是一种可怕的语言,不是吗?
奖励:所有指针通常具有相同的位表示,但具有不同的运算符[]
,->
,*
,++
,--
,{{1 },+
。
答案 3 :(得分:0)
最简单和最通用的答案是内存维护,C语言中的每个变量在我们声明时都会在主内存(RAM)中保留一些内存空间,例如:
unsigned int var;
将保留2 or 4
个字节,其范围为0 to 65,535
或0 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位的不同。