整形在内存中的存储-原码补码反码的理解与应用

目录

一、概论

1.1 C语言中基本的数据类型

1.2 类型的基本归类

二、整形在内存中的存储

2.1 原码、反码、补码

2.2 存储补码和大小端存储

三、计算各基本数据类型的范围计算原理

3.1 有符号类型的整形范围

3.2 无符号类型的整形范围

3.3 例题


一、概论

C语言提供了非常多的数据类型,我们可以用sizeof来计算它们在内存中所占的字节数,我们今天想要深入了解它们存储的底层原理。

1.1 C语言中基本的数据类型

char - 字符型 所占字节为1
short - 短整型 所占字节为2
int - 整形 所占字节为4
long - 长整型 所占字节>=4,通常为4或8
long long - 更长的整形 所占字节为8
float - 单精度浮点型 所占字节为4
double - 双精度浮点型 所占字节为8

类型的意义:

1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。

2. 如何看待内存空间的视角。

简单来说,我们要搞明白不同数据类型是怎么存进去的怎么拿出来的

1.2 类型的基本归类

整形家族:
char
 unsigned char
 signed char
short
 unsigned short [int]
 signed short [int]
int
 unsigned int
 signed int
long
 unsigned long [int]
 signed long [int]

浮点数家族:
float
double

构造类型:
> 数组类型
> 结构体类型 struct
> 枚举类型 enum
> 联合类型 union

指针类型:
int *pi;
char *pc;
float* pf;
void* pv;

空类型:
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型。

二、整形在内存中的存储

2.1 原码、反码、补码

1)原码、反码、补码就是整数的二进制序列;

2)正整数的原码、反码和补码完全一致;

3)负整数的原码、反码和补码不一样,需要相互转换。

4)内存中存储的是补码,拿出来用的是原码

下面我们分别定义正整数a=10,负整数b=-10,由于是int类型,它们都占4个字节,用32个二进制位表示

a的二进制序列,也是它的原码、反码、补码:

整形在内存中的存储-原码补码反码的理解与应用_第1张图片

b的原码、反码和补码:

整形在内存中的存储-原码补码反码的理解与应用_第2张图片

结论:

1)正整数的原码、反码和补码是一致的;

2)负整数的原码转化把符号位以外的二进制位按位取反得到反码,反码+1得到补码。

2.2 存储补码和大小端存储

数据在内存中存储,以十六进制位表达:

整形在内存中的存储-原码补码反码的理解与应用_第3张图片

内存中存储的是变量的补码,我们来验证一下,把a和b的补码转化为十六进制位:

整形在内存中的存储-原码补码反码的理解与应用_第4张图片

注:a是正数,a的原码反码补码一致。

整形在内存中的存储-原码补码反码的理解与应用_第5张图片 注:b是负数,要先写出-10的原码,然后转化为反码,反码再转化为补码。

 验证一下:
整形在内存中的存储-原码补码反码的理解与应用_第6张图片

整形在内存中的存储-原码补码反码的理解与应用_第7张图片

结果表明内存中存储的是补码,但是是反过来存储的,这就涉及到大小端字节序的概念

大端存储:数据的位保存在内存的地址中,位保存在内存的地址中。

小端存储:数据的位保存在内存的地址中,位保存在内存的地址中。

对于a来说,整形在内存中的存储-原码补码反码的理解与应用_第8张图片

对于b来说,

整形在内存中的存储-原码补码反码的理解与应用_第9张图片

因此a和b的大端字节序和小端字节序存储分别是:

 整形在内存中的存储-原码补码反码的理解与应用_第10张图片

整形在内存中的存储-原码补码反码的理解与应用_第11张图片

 结论:在当前电脑的机器环境下,数据在内存中是以小端字节序存储的。

一道判断大小端存储的面试题:

请简述大小端字节序的概念,并设计一个程序判断当前机器的字节序。

在上面解释了大小端的概念,那如何判断当前机器的字节序呢?我们想要得到当前变量在内存中存储的十六进制序列,为了这个序列尽可能的简单,我们就定义一个整形变量让它的值为1,那么整形1的十六进制表达是0x00000001

如果是小端字节序存储,那么就是 (低地址) 01     00      00      00(高地址) 

如果是大端字节序存储,那么就是 (低地址) 00     00      00      01(高地址)

我们只想访问其中1个字节,得到整形1的高位(或者低位),解引用看一看到底是1还是0,那么就能判断到底是大端存储还是小端存储

整形在内存中的存储-原码补码反码的理解与应用_第12张图片

原本的变量是int*类型的,*解引用后得到的是4个字节,那么我们将其强制类型转化为char*类型,*解引用后就只得到1个字节

int main()
{
	int a = 1;
	char* p = (char*)&a;
	if (*p == 1)
	{
		printf("小端字节序存储\n");
	}
	else
	{
		printf("大端字节序存储\n");
	}
	return 0;
}

整形在内存中的存储-原码补码反码的理解与应用_第13张图片

三、计算各基本数据类型的范围计算原理

整形在内存中的存储-原码补码反码的理解与应用_第14张图片

 整形在内存中的存储-原码补码反码的理解与应用_第15张图片

参考来源:https://www.cnblogs.com/luofay/p/6070613.html

注:1个字节的类型的二进制序列有8位,2个字节的有16位,4个字节的有32位,8个字节的有64位,由于变量在内存中存储的是补码,补码的二进制序列和对应类型所占的字节数量有关,因此各基本数据类型的范围和它们所占字节的大小有关。

3.1 有符号类型的整形范围

我们以char类型举例:

char类型由于只占1个字节的内存,所以它的二进制序列只有8位,所以我们容易列举出char类型二进制序列的所有可能,即从00000000~11111111。

我们一般默认char就是signed char,就是有符号的char,因此它的二进制序列的第一位要看成符号位,那么01111111和1000000就应该是char类型范围的两个边界,如图所示: 

整形在内存中的存储-原码补码反码的理解与应用_第16张图片

整形在内存中的存储-原码补码反码的理解与应用_第17张图片

 其实,在这255组可能的序列中,我们除了10000000这个序列无法正常计算出它的原码,其他254组二进制序列我们都可以用正数原反补相同、负数要相互转化的原理来计算出它们的原码以及对应的十进制数,所以规定10000000这个序列的原码对应的十进制数为-128。

因此有符号char类型的范围就是-128~127

剩下short类型、int类型、long类型和long long类型的范围计算原理是一致的,只不过由于它们所占字节数不同,就要用不同长度的二进制序列表示,二进制序列一长,2的所占权重可能就增大,所以范围就相应地比char类型要大的多了

int类型的范围是-2^31 ~ (2^31-1),short类型的范围是-2^15 ~ 2^15-1

3.2 无符号类型的整形范围

我们容易观察表知,无符号类型的范围就是0~a,a是对应有符号类型的范围的长度

比如char的范围是-128~127,unsigned char的范围就是0~255;int的范围是-2^31 ~ (2^31-1),unsigned int的范围就是0~2^32-1;short的范围是-2^15 ~ 2^15-1,unsigned short的范围是0~2^16-1等等。

我们以unsigned char类型举例,与char进行对比:

整形在内存中的存储-原码补码反码的理解与应用_第18张图片

由于是无符号类型,所以第一位就不需要看成符号位,因此在内存中存储的补码和拿出来用的时候的原码是一致的。

3.3 例题

解题步骤:

1)先写出变量值的原码;

2)根据是正数还是负数,写出反码和补码;

3)根据变量的类型,判断数据是否完全存储进这个变量,不完全存储则发生截断

4)观察这个变量是有符号类型还是无符号类型,对应地发生整形提升:有符号位高位补符号位,无符号位高位补0

5)观察是以什么方式打印出来的(以什么方式使用)以及变量本身是有符号还是无符号,根据补码计算出原码。

例题一

int main()
{
	char a = -1;
	signed char b = -1;
	unsigned char c = -1;
	printf("%d\n%d\n%d\n", a, b, c);
	return 0;
}

解:a和b是一致的,在vs环境下char就是signed char,因此a和b打印出来是一样的。

整形在内存中的存储-原码补码反码的理解与应用_第19张图片

c是无符号类型,所以在整形提升和最后的原码计算方面和a、b不一致。

整形在内存中的存储-原码补码反码的理解与应用_第20张图片

 整形在内存中的存储-原码补码反码的理解与应用_第21张图片

 例题二

int main()
{
	char a = -128;
	printf("%u", a);
	return 0;
}

解:

整形在内存中的存储-原码补码反码的理解与应用_第22张图片

整形在内存中的存储-原码补码反码的理解与应用_第23张图片

例题三 

int main()
{
	char a = 128;
	printf("%u", a);
	return 0;
}

解: 

整形在内存中的存储-原码补码反码的理解与应用_第24张图片

 整形在内存中的存储-原码补码反码的理解与应用_第25张图片

 例题四

int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d", i + j);
	return 0;
}

 解:由于此时i和j都是int类型,可以完全存储数据,所以不需要发生截断

整形在内存中的存储-原码补码反码的理解与应用_第26张图片

整形在内存中的存储-原码补码反码的理解与应用_第27张图片 

 例题五

int main()
{
	unsigned int i = 9;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
		Sleep(1000);
	}
	return 0;
}

解:

整形在内存中的存储-原码补码反码的理解与应用_第28张图片

这是一个死循环,说明循环不仅仅是正常从9执行到0。

i是无符号整形,说明i恒为正数,前9到0是正常的打印,后面的负数有可能转化为正数,所以我们考虑负数转为无符号整数打印出来的是怎样的数。

而且此时也不需要发生截断。

整形在内存中的存储-原码补码反码的理解与应用_第29张图片整形在内存中的存储-原码补码反码的理解与应用_第30张图片

 

 

 

你可能感兴趣的:(c语言)