深度理解C语言数据在内存中的存储
在平时写C代码的时候,我们只知道一些数据类型变量占多少个字节,但是不知道数据在内存中是怎么存储的,今天我就带大家来详细学习一下C语言数据在内存中的存储
一.数据类型的介绍
关键字 | 类型 | 大小(单位:字节) |
---|---|---|
char | 字符类型数据 | 1 |
short | 短整型 | 2 |
int | 整形 | 4 |
long | 长整型 | 4 |
long long | 更长的整形 | 8 |
float | 单精度浮点型 | 4 |
double | 双精度浮点型 | 8 |
看着挺多其实可以分为两大类整型家族还有浮点型家族。
1.1 整型家族
char
signed char
unsigned char
short
signed short
unsigned short
int
signed int
unsigned int
long
signed long
unsigned long
注:char
类型在内存中存储的字符是ASCII码值,ASCII值是整形,所以char
归类到整型家族。
1.2 浮点数家族
float
double
1.3构造类型(自定义类型)
数组类型
结构体类型 struct
枚举类型 enum
联合类型 union
1.4 指针类型
int *pi;
char *pc;
float *pf;
void *pv (无具体类型的指针)
1.5 空类型
void 表示空类型
注:通常应用于函数的返回类型、函数的参数、指针的类型
类型的作用
1.确定这个类型的变量在内存中所占空间的大小
2.以对应的类型拿出数据
了解完了以上知识,就能对C语言的数据类型有一个基础的认识,我们接下来就进入正题,重点来咯!
二. 整型在内存中的存储
2.1原码、反码、补码
要理解数据在内存中的存储,我们要知道这么一个东西:原码、反码、补码。整数在计算机中的表示方法:
三种表示方法都有符号位和数值位两部分,符号位0表示“正”,1表示“负”
原码:将整形数据直接写出对应的二进制数
反码:符号位不变,原码按位取反(1变0、0变1)得到反码
补码:反码加1得到补码
注:
正数的原码、反码、补码都相同
负数的原码、反码、补码遵循上面的规则
举个例子:
int a = -10 占4个字节——32个bit位
原码:10000000000000000000000000001010
反码:11111111111111111111111111111110101
补码:11111111111111111111111111111110110
最高位为符号位,原码转成反码时符号位不变!!变量a在内存中存储的是它的补码int a = 10
原码:00000000000000000000000000001010
反码:00000000000000000000000000001010
补码:00000000000000000000000000001010
正数的原反补相同
到这里可能就会有很多人有一个疑问:我们放着好好的原码不用,为什么要用补码去存呢?
其实这也有其中的道理
举个简单的例子,假设我们是以原码来存放数据类型的。
int a = 10;
原码:00000000000000000000000000001010
int b = -10;
原码:10000000000000000000000000001010
int c =a+b;
00000000000000000000000000001010
10000000000000000000000000001010
10000000000000000000000000010100
10 + (-10) = -20怎么可能呢
从上面可以看出,用原码在进行数学计算的时候会出现错误,于是科学家就想出了存储补码来进行计算。
int a = 10;
补码:00000000000000000000000000001010
int b = -10;
补码:11111111111111111111111111110110
int c =a+b;
00000000 00000000 00000000 00001010
11111111 11111111 11111111 11110110
c的补码:
1 00000000 00000000 00000000 00000000
这里的 1 存不下直接丢掉!
这样计算出来的结果是全 0 转换成十进制也是 0 这就符合数学计算了。事实上,科学家比我们考虑的多,原码按位取反得到反码,反码在+1得到补码,补码按位取反得到反码,反码再+1也能得到原码。这样计算机中在只有加法运算和一套原码转反码运算的情况下就能解决很多的运算。
三.大小端介绍
大端存储:数据的高位字节序存放在低地址处,数据的低位字节序存放在高地址处
小端存储:数据的高位字节序存放在高地址处,数据的低位字节序存放在低地址处
大端和小端在内存中存储的具体情况:
我们也可以通过在vs上通过调试观察出来
可以看出来,44放在614地址处,11放在617地址处,即低位字节放在低地址,高位字节放在高地址编译器
3.1 测试
写一个程序,判断你的计算机是大端存储还是小端存储
思路:定义一个int
整形变量,拿出第一个字节中的数据,看看是数据的高位还是低位,如果是数据的高位说明低地址处存放的高位字节即为大端存储,如果拿出来的是数据的低位说明低地址处存放的是低位字节即为小端存储。
代码实现:
int check_sys()
{
int a = 1;
char* p = (char*)&a;
return *p == 1;
}
int main()
{
if (check_sys())
printf("小端存储\n");
else
printf("大端存储\n");
return 0;
}
我的电脑就是小端存储
你们也可以看看自己的电脑是大端存储还是小端存储。
四. 浮点数在内存中的存储
4.1 浮点数存储规则
我们首先要知道小数如何用二进制的表示,对于二进制整数每一位上的数都有自己的权重为2的1次方、2的2次方、2的3次方…。小数位也有自己的权重为2的-1次方、2的-2次方、2的-3次方依次往后
1 | 1 | 1 | 1 | . | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
2 3 2^{3} 23 | 2 2 2^{2} 22 | 2 1 2^{1} 21 | 2 0 2^{0} 20 | 小数点 | 2 − 1 2^{-1} 2−1 | 2 − 2 2^{-2} 2−2 | 2 − 3 2^{-3} 2−3 | 2 − 4 2^{-4} 2−4 |
4.2浮点数存储形式:
根据国际标准IEEE(电子和电子工程协会)754,任意一个二进制浮点数V可以表示为下面的形式:
( − 1 ) S ∗ M ∗ 2 E (-1) ^ S * M * 2 ^ E (−1)S∗M∗2E
1. ( − 1 ) S (-1) ^ S (−1)S表示符号位,当S=0时,V为正数;当S=1时,V为负数
2. M 表示有效数字,且1 <= M <2
3. 2 E 2 ^ E 2E表示指数位
float型(32位):
IEAA规定:对于32位的浮点数,最高的1位是符号位S,接着的是8位的指数E,剩下的23位为有效数字M
单精度浮点数存储模型:
double型(64位):
对于64位的浮点数,最高的1位是符号位S,接着的是11位的指数E,剩下的52位为有效数字M
双精度浮点数存储模型:
4.3 IEEE 754对有效数字M和指数E的规定
- 有效数字M:
EEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存小数部分。比如保存1.0110001101时,只保存0110001101,后面的位数补0就可以了,等到读取的时候,再把第一位的1补上去。
- 指数E
E为一个无符号整数(unsigned int)根据指数域不同取值分为一下三种情况:
- E不全为0或不全为1(规格化值)
这是最常见情况,取出内存中的数时,指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。- E全为0(非规格化值)
这时,浮点数的指数E等于1-127(或1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为 0.xxxxxxx的小数。这样做是为了表示正负零,以及接近于0的很小的数字。- E全为1(特殊数值)
当指数域全为1时属于这种情形。此时,如果小数域全为0且符号域S=0,则表示正无穷,如果小数域全为0且符号域S=1,则表示负无穷。如果小数域不全为0时,浮点数将被解释为NaN, 即不是一个数(Not a Number)
给大家举个例子:
float f =5.5;
二进制:101.1
标准表示形式为: ( − 1 ) 0 (-1) ^ 0 (−1)0*1.011* 2 2 2^{2} 22
S = 0
E = 2
M = 1.011
S位要存0
E位要存129(2+127)
M位要存011后面补零
即:0 10000001 01100000000000000000000
用十六进制表示就是:40 b0 00 00
在vs里面看一下也是如此
相信你看到这会对C语言数据存储有更深的理解,那么本章就到此结束喽,砸门下一篇文章见!.