计算机组成原理(1)- 整数的存储

初学C++时,书上或者网上的博客中会讲“整数在内存中存储的是补码”,一直也没有理解为什么要以补码存储,以及补码到底是什么。最近看了计算机组成原理,有所感悟,遂于此记录,如有错误欢迎大家指出。

先打一段代码,三行打印分别是什么?

	unsigned int a = -1;
	printf("a = %x\n", a);	// a = ffffffff
	printf("a = %u\n", a);	// a = 4294967295
	printf("a = %d\n", a);	// a = -1

以前的理解可能是这样:aunsigned inta的范围是0~2^32-1,将 -1 赋值给a会出现溢出,所以第二行打印是4294967295。

现在觉得这样理解有一些僵硬,不易于理解,接下来就记录下我对整数存储的理解。


1、二进制加减法

在开始之前,首先要了解下二进制加减法:对于一个32位寄存器来说,它最大可以记录的值为0xffffffff,也就是所有位都为1

11111111 11111111 11111111 11111111

在最大值基础之上再加一会变成多少呢?加一之后每一位都会向前进1,由于寄存器并没有第33位,所以留下低32位,变成0x0

  11111111 11111111 11111111 11111111
+ 00000000 00000000 00000000 00000001
=100000000 00000000 00000000 00000000

在0的基础之上减一会变成多少呢?由于0比1小,所以最低位会向前一位借位,前一位再向它的前一位借位,一直到第32位,它会向第33位借位(其实是不存在的),最后得到结果0xffffffff

 100000000 00000000 00000000 00000000
- 00000000 00000000 00000000 00000001
= 11111111 11111111 11111111 11111111

2、无符号整数

符号指的是-负号和+正号,无符号整数指的是大于等于0的正整数。

无符号整数的存储很简单,寄存器中/内存中的值等于无符号整数的二进制值,例如无符号整数10,它的二进制数为0b1010,所以它在寄存器中为

00000000 00000000 00000000 00001010

当寄存器所有位都为1时就是无符号整数的最大值,十六进制值为0xffffffff,十进制值为2^32-1。所以无符号整数的范围为0 ~ 2^32-1

我们在第一节了解到0xffffffff加1会变成0x0,所以无符号整数最大值加一会变成零,这种情况称为加法溢出

	unsigned int a = 0xffffffff;
	printf("a = %x\n", a);	// ffffffff
	printf("a = %u\n", a);	// 4294967295
	unsigned int b = a + 1;
	printf("b = %x\n", b);	// 0
	printf("b = %u\n", b);	// 0

同样的,0x0减1会变成0xffffffff,即零减一会变成最大值,这种情况称为减法溢出

	int b = 0;
	unsigned int c = b - 1;
	printf("b = %x\n", c);	// ffffffff
	printf("b = %u\n", c);	// 4294967295

画一张图理解下无符号整数的范围以及溢出:
计算机组成原理(1)- 整数的存储_第1张图片


3、有符号整数

无符号整数表示0 ~ 2^32-1之间的正数,如果想要一个负数应该怎么办呢?这时候就发明了有符号数,规定最高位为符号位,符号位为1则该数为负数,符号位为0则为正数

有符号正整数的存储方式和无符号整数的存储方式相同,唯一的不一样是,由于最高位为符号位,所以有符号整数的最大值为0x7fffffff,对应十进制值为2^31-1

01111111 11111111 11111111 11111111

接下来看内存中负数应该如何存储呢?

我们先从十进制的角度理解负数,十进制中的 -1 = 0 - 1-2 = 0 - 2,以此类推 -n 等于 0 减 n。

二进制中也是同样道理,-10 就是 0b00b1010,得到0xfffffff6。因此-10 在内存中的值是0xfffffff6

  00000000 00000000 00000000 00000000
- 00000000 00000000 00000000 00001010
= 11111111 11111111 11111111 11110110

有符号整数可以表示的负数的最小值是多少呢?上面我们讲到最高位为符号位,负数的符号位为1,所以最小值对应的内存中的值应该是0x80000000,我们来计算一下:

  00000000 00000000 00000000 00000000
- ?
= 10000000 00000000 00000000 00000000

计算得到?处的值是0x80000000,对应的十进制值为-2^31,所以有符号整数的范围为-2^31 ~ 2^31-1

同样的,有符号整数也会有溢出的情况出现:

最大值2^31-1(0x7fffffff)加1会变成0x80000000(-2^31),一下子从最大变成了最小值,

  01111111 11111111 11111111 11111111
+ 00000000 00000000 00000000 00000001
= 10000000 00000000 00000000 00000000
	int a = 0x7fffffff;
	printf("a = %x\n", a);	// a = 7fffffff
	printf("a = %d\n", a);	// a = 2147483647
	int b = a + 1;
	printf("b = %x\n", b);	// b = 80000000
	printf("b = %d\n", b);  // b = -2147483648

最小值-2^31(0x80000000)减1会变成0x7fffffff(`2^31-1``),一下子从最小变成了最大值,这里就不贴代码了。

  10000000 00000000 00000000 00000000
- 00000000 00000000 00000000 00000001
= 01111111 11111111 11111111 11111111

计算机组成原理(1)- 整数的存储_第2张图片


3 原码、补码、反码

前面两节了解了有符号数,无符号数,以及正数和负数在内存中的存储方式。再回顾一下:正数在内存中存储的值就是其二进制数,负数在内存中存储的值需要用0减去其绝对值的二进制数。

这时候就发现将二进制值转换为负数,或者将负数转化为二进制值要比正数麻烦很多。或者说是负数在内存中的存储方式很不符合我们的阅读习惯。

举个例子,我们平时更习惯将-10表示为0b10000000 00000000 00000000 1010,符号位为1说明是一个负数,这样可以很快知道这个二进制数表示的是-10

我的理解是:将这种符合阅读习惯的二进制数称为原码,将计算机内存中实际存储的内容称为补码

我们如何从这个原码计算得到它在内存中真正存储内容(补码)呢?

先看正数10,我们以原码的形式表示它,得到0b1010,等于内存实际存储内容,所以说正数的原码等于补码。

00000000 00000000 00000000 1010

再看负数-10,它在内存中的存储值(补码)就是将0b1010(10)取反再加1,将取反得到的值称为反码

  11111111 11111111 11111111 0101
+ 00000000 00000000 00000000 0001
= 11111111 11111111 11111111 0110

4 数据的存储与读取

回到一开始的例子

	unsigned int a = -1;
	printf("a = %x\n", a);	// a = ffffffff
	printf("a = %u\n", a);	// a = 4294967295
	printf("a = %d\n", a);	// a = -1

无符号数a的值为-1,我们不需要了解是否发生溢出了,我们只需要知道内存中存储的值是多少,这里的值是-1,所以内存中的值为0xffffffff

我们读取到的十进制数是多少取决于我们以什么格式去读取,如果以%u无符号数读取,它就是4294967295;如果以%d有符号数读取,它就是-1。

你可能感兴趣的:(计算机组成原理,c语言)