【落羽的落羽 C语言篇】数据存储简介
文章目录
- 一、整型提升
- 1. 概念
- 2. 规则
- 二、大小端字节序
- 1. 概念
- 2. 练习
- 练习1
- 练习2
- 三、浮点数在内存中的存储
- 1. 规则
- 2. 练习
一、整型提升
1. 概念
C语言中,整型算术运算至少是以“缺省整型类型”(int)的精度来进行的。为了达到这个精度,比int类型精度低的char和short等短整型类型的操作数,在使用前会转换成普通整型(int),这种转换被称为整型提升。
不太好理解,我们先看一个奇怪的现象:
char a = 'a';
char b = 'b';
char c = a + b;
printf("a的ASCII值:%d\n", a);
printf("b的ASCII值:%d\n", b);
printf("c的值:%d\n", c);
你觉得这段代码的结果是什么样的?c的值是不是a和b的ASCII值相加呢?
很反直觉的结果。
这是因为a和b是char类型的数据,a+b在进行计算前,要进行整型提升,然后执行加法运算得到一个占据4字节的int类型数据,但这个数据要存储在只有一字节大小的c里呀。所以结果会被截断,然后再存储进c中。具体规律我们再来看:
2. 规则
进行整型提升的规则是:
- 有符号整数的提升:补码高位补充符号位数
- 无符号整数的提升:补码高位补充0
以char类型为例,char也属于整型家族,是有符号的类型
假如有char a = -1;
,-1本来是一个十进制数,会占据四个字节,补码是11111111 11111111 11111111 11111110,但变量a只有八个比特位,只能存下前八位11111111。
如果之后的整数运算用到了变量a,就要先对a进行整型提升,11111111的符号位是1,高位补充1,结果是11111111 11111111 11111111 11111111(补码),即十进制数-1。
假如有char b = 1;
,1本来是一个十进制数,会占据四个字节,补码是00000000 00000000 00000000 00000001,但变量b只有八个比特位,只能存下前八位00000001。
如果之后的整数运算用到了变量b,就要先对b进行整型提升,00000001的符号位是0,高位补充0,结果是00000000 00000000 00000000 00000001(补码),即十进制数1。
上面的两个例子中,a和b进行整型提升后,它们的值仍是-1和1。
但假如有char c = 128;
,是什么结果呢?128本来是一个十进制数,会占据四个字节,补码是00000000 00000000 00000000 10000000,但变量c只有八个比特位,只能存下前八位10000000。这时,符号位变成了1。后续使用变量c进行整数运算时,要先对它整型提升,高位补充符号位1,结果是11111111 11111111 11111111 10000000(补码),是十进制数-128!
通过这个规律,我们可以总结出一个很重要的规则:由于整型提升的存在,char类型只有八个二进制位,char类型的变量只能存放-128 ~ 127的数,是一个循环。如果有char x = 127, y = x+1;
那么y的值实际上就是-128。
而若是unsigned char类型变量,由于是无符号类型,没有符号位,八个二进制位能表示出的最大十进制数是255,这种类型变量就只能存放0 ~ 255的数。
同样的道理,short类型的变量只能存放-32768 ~ 32767的数。
举个栗子:
char a = -128;
printf("%u",a);//%u是十进制的无符号整数
这段代码的结果是一个42亿多的数字。这是因为-128的补码本来是11111111 11111111 11111111 10000000,但a中只能存放前八位10000000。打印a也是一种对a的运算,要先进行整型提升。符号位是1,提升后是11111111 11111111 11111111 10000000。但%u说明这是一个无符号整数,最高位的1也要计算,最后这个32个二进制位换算成十进制就是42亿多的一个数。
二、大小端字节序
1. 概念
我们之前已经了解过整数在内存中的存储方式了——补码。
我们再来观察一个细节:
调试的时候,我们可以看到a中的0x11223344这个十六进制数是按着字节为单位,在内存中倒着存储的。(四个二进制数换算成一个十六进制数,也就是两个十六进制数占据一个字节)
超过一个字节的数据在内存中存储的时候,就会有存储顺序的问题,按照正序存放或是逆序存放,我们分为大端字节存储和小端字节存储模式:
-
大端字节存储模式:数据的低位字节内容保存在内存的高地址处,数据的高位字节内容保存在内存的低地址处。如数0x1123344在内存中,四个地址由低到高存放的分别是11 22 33 44。
-
小端字节存储模式:数据的低位字节内容保存在内存的低地址处,数据的高位字节内容保存在内存的高地址处。如数0x1123344在内存中,四个地址由低到高存放的分别是44 33 22 11。
回看上图,可以看出我的系统环境是小端字节存储模式。
2. 练习
练习1
百度的一道笔试题:设计一个小程序来判断当前机器的字节序
思路:我们将整数1存放到一个int变量中,看一看它四个字节中最地址最低的数据是什么。1的十六进制是0x00000001,四个字节由地址低到高:如果是大端应该是00 00 00 01,如果是小端就应该是01 00 00 00
#include<stdio.h>
int check()
{
int i = 1;
return *(char*)&i;
//char*强制类型转换&i,再解引用,就能找到i的四个当中地址最低的字节的内容,00或01
}
int main()
{
int ret = check();
if(ret==1)//如果ret是1,说明i中的1存放形式是01 00 00 00,是小端
printf("小端");
else//如果ret是0,说明i中的1存放形式是00 00 00 01,是大端
printf("大端");
return 0;
}
练习2
这段代码的结果是什么
//X86环境,小端字节序模式
#include<stdio.h>
int main()
{
int a[4] = {1,2,3,4};
int* p1 = (int*)(&a+1);
int* p2 = (int*)((int)a+1);
printf("%x,%x",p1[-1],*p2);//%x是十六进制数,但VS打印会省略非0数前的所有0和x
return 0;
}
三、浮点数在内存中的存储
1. 规则
浮点数类型包括float、double、long double等
浮点数在内存中的存储方式相对复杂:
根据国际标准IEEE 754,任意一个二进制浮点数V都可以表示成下面的形式:
V = (-1)S × M × 2E
- (-1)S 表示符号位,当S=0时,V为整数。当S=-1时,V为负数。
- M表示有效数字,M大于等于1,小于2。
- 2E 表示指数位。
举例,十进制的5.0,写成二进制是101.0,相当于1.01×22。按照上面V的格式,S=0,M=1.01,E=2。十进制的-5.0,写成二进制是-101.0,相当于-1.01×22。按照上面V的格式,S=1,M=1.01,E=2。
同时,IEEE 754还规定:
- 对于32位的浮点数(float),最高的1位存储符号位S,接着的8位存储指数E,剩下的23存储有效数字M。
- 对于64位的浮点数(double),最高的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M。
前面说过,1<=M<2,也就是说,M的个位一定是1。因此,IEEE 754还规定,在计算机内部保存M时,默认这个数第一位总是1,因此可以被省略,只保存小数部分。比如保存1.012345时,只保存012345,等到读取的时候,再把第一位的1加上去。这样做的目的是节省一份空间,相对于可以多存储一位数字。
至于指数E,情况就比较复杂。
E是一个无符号整数(unsigned int)。如果E为8位,它的取值范围是0 ~ 255;如果E为11位,它的取值范围是0 ~ 2047。但是,科学计数法的E可以是负数。所以IEEE 754还规定,存入内存的真实值必须再加上一个中间数,对于8位的E中间数是127,对于11位的E中间数是1023。举个例子,如果一个浮点数的E是12,它保存成32位浮点数时,E会被保存成12+127=139,即10001011;它保存成64位浮点数时,E会被保存成12+1023=1035,即10000001011。
指数E从内存中取出还可以分为三种情况:
- E中既有0也有1:将E的二进制序列换算成十进制数,减去127(或1023),得到真实值
- E全为1:如果M不是0的话,V就是正负无穷大
- E全为0:V是无限接近于0的很小的数字
理解就好~
2. 练习
通过前面的知识,思考一下这段代码的结果是什么:
#include<stdio.h>
int main()
{
int n = 9;
float* p = (float*)&n;
*p = 9.0;
printf("%d", n);
return 0;
}
答案是:
解析:
将float类型的9.0存入n的32的比特位时,要遵循32位浮点数的存储规则。9.0的二进制是1001.0,即(-1)0 × 1.001 × 23,那么,第一位的符号位E是0,有效数字M等于001后面再加20个0补全23位,指数E等于3+127等于130,即10000010 。
所以,9.0在内存中的存储是:0 10000010 00100000000000000000000,但如果要被当成整数来解析时,这就是整数在内存中的补码了,直接换算成十进制的结果就是1091567616。
本篇完,感谢阅读