在C语言中,为编程者提供了很多数据类型,不仅有内置的数据类型,还有自定义数据类型。内置数据类型有char、int、long、float、double等,自定义数据类型有结构等,那么这些内置数据类型的数据在内存中是怎么存储的呢?
这里只讲解内置数据类型,因为自定义数据类型的存储与内置数据类型的存储不同,那么C语言的内置数据类型有下面几种。
/*整数家族*/
char //字符型
unsigned char //无符号字符型
short //短整型
unsigned short //无符号短整型
int //整型
unsigned int //无符号整型
long //长整型
unsigned long //无符号长整型
long long //更长的整型
unsigned long long //无符号更长的整型
/*浮点数家族*/
float //单精度浮点数
double //双精度浮点数
long double //更长的双精度浮点数
在这里我们把内置的数据类型分成整数家族和浮点数家族,因为它们在内存中的存储并不相同。
一、整数家族在内存中的存储:
整数在内存中的存储比较简单,我们都知道整数在内存中的存储是以补码存储的,还不了解原码、反码、补码的可以参考我这篇文章:一次讲透原码、反码和补码_爱吃柠檬呀的博客-CSDN博客。那么我们在这里探讨一下为什么是以补码的形式存储,而不是原码或反码。
举例:-10 + 5 = -5(我们这里以8个低位来计算)
-10的原码:10001010
-10的反码:11110101
-10的补码:11110110
5的补码: 00000101
相加之后的补码:11111011
将这个二进制序列转化为反码:10000100
转化为原码:10000101,我们发现这个二进制序列就是-5,这是按照在内存中以补码的形式存储计算的。
按照原码计算:-10 + 5 = -5
-10的原码:10001010
5的原码: 00000101
相加之后的原码:10001111,这个二进制序列是-15还是143我们无从得知,因为用原码存储的话,最高位到底是符号位还是数值位我们并不清楚,另外这两个结果也和计算的结果是-5不相符,所以整数在内存不可能以原码的形式存储。
按照反码计算:-10 + 5 = -5
-10的原码:10001010
-10的反码:11110101
5的反码: 00000101
相加之后的反码:11111010,这个二进制序列是-122还是250也同样不得知,所以整数在内存中的储存也不可能是反码。反码是原码与补码之间相互转换的中间产物,所以如果是以反码存储,那还要补码干什么?
综上,我们就知道了整数在内存中就是以补码形式存在的。
二、大小端介绍:
大小端是知道整数在内存中是以补码的形式存储的之后,探讨在补码在内存到底是以怎么的方式存储的,这就涉及到了大小端。
大端就是把数据的低位字节的数据存放到高地址处,高位字节的数据存放到低地址处。
小端就是把数据的低位字节的数据存放到低地址处,高位字节的数据存放到高地址处。
那么为什么会有大端存储和小端存储呢?实际上不仅要把数据放到内存中方便,还要方便取出来,所以在存储时就有了这两种储存模式,在VS编译器中,数据就是以小端的形式存储的。
三、浮点数家族在内存中的存储:
浮点数在内存中的存储就与整数完全不一样了,整数只是简单的使用补码存储,而浮点数比较麻烦。
浮点数在内存中并不会精确保存,这也很好理解,现实中也有一些十进制浮点数是不能精确表示的,比如循环小数等,所以将一个浮点数转换为二进制浮点数时,不能精确存储也就很好理解了。
float和double也不能存的下所有的小数,所以有一些浮点数在内存中不能精确保存。
浮点数的存储要以V = (-1) ^ S * M * 2 ^ E这种形式存储,其中(-1) ^ S表示符号位,当S = 0时,V为正数。当S = 1时,V为负数,M表示有效数字,范围是1,2 ^ E表示指数位。比如十进制5.5在内存中的存储结果为(-1)^0*1.011*2^2。首先十进制5.5转换为二进制是101.1,然后把小数点向左移动两位就变成1.011*2^2(因为是二进制,所以指数形式的底数是2),最后加上符号位即可。
下面举个例子:
//浮点数在内存中的存储
#include<stdio.h>
int main()
{
float f = 5.5;
//5.5的二进制表示为101.1
//对应的形式就是(-1)^0 * 1.011 * 2^2
//E的值是2,加上127是129,对应的二进制是10000001
//转换成二进制:0 10000001 01100000000000000000000
//转换二进制时,没有用到的位用0补齐
//将二进制序列转换成十六进制验证:40B00000
printf("%x\n", *(int*)&f);//打印结果为:40b00000
return 0;
}
我们知道了浮点数在内存中的存储,那么该怎么从内存中取出来呢?这就分为以下是三种情况:
当E不为全0或不为全1时,读取E时需要加上-127(32位)或加上-1023(64位),有效数字M加上第一位的1,这也是最常见的一种情况,怎么存储的就怎么读取出来。
当E为全0时,浮点数的指数E = 1 - 127(32位)或E = 1 - 1023(64位)即为真实值。有效数字M不加第一位的1,直接还原成0.xxxxxxx的小数,这样做是为了表示±0,一个非常接近0的数字。
因为当E为全0时,那么原来的值应该是-127(因为存储需要加上127),此时的E是一个非常非常小的数字,接近0的数字。这个数字相当于1/2^127,这个数字是多小已经不重要了,因为计算机已经把它当成0计算了。当E = 1 - 127(32位)时,E的值是-126,表示的数是1/2^126,虽然没有1/2^127小,但是也是非常接近0的数字了。
当E为全1时,浮点数是一个非常大的数字,32位时有2^128,64位有2^1023,已经接近无穷大了。
说白了,如果E不全0或全1时,怎么存的就怎么取,如果全0,编译器就会默认当成0(这是一个非常非常小的数字),如果全1,编译器就默认当成无穷大(这也是一个非常非常大的数字)。
四、补充知识:
这里补充一下数据类型的取值范围:
(具体可以参考我的另一篇文章:C语言中的数据类型_爱吃柠檬呀的博客-CSDN博客)
数据类型 | 字节数 | 取值范围 |
---|---|---|
char(有符号字符型) | 1 | -128 ~ 127(2^7~2^7-1) |
unsigned char(无符号字符型) | 1 | 0 ~ 255(0~2^8-1) |
short(有符号短整型) | 2 | -32768 ~ 32767(-2^15~2^15-1) |
unsigned short(无符号短整型) | 2 | 0 ~ 65535(0~2^16-1) |
int(有符号整型) | 4 | -2147483648 ~ 2147483647(-2^31~2^31-1) |
unsigned int(无符号整型) | 4 | 0 ~ 4294967295(0~2^32-1) |
long(有符号长整型) | 4 | -2147483648 ~ 2147483647(-2^31~2^31-1) |
unsigned long(无符号长整型) | 4 | 0 ~ 4294967295(0~2^32-1) |
long long(有符号更长的整型) | 8 | -9223372036854775808 ~ 9223372036854775807(-2^63~2^63-1) |
unsigned long long(无符号更长的整型) | 8 | 0 ~ 18446744073709551615(0~2^64-1) |
float(单精度浮点型) | 4 | 1.17549×10^(-38)~3.40282×10^(38)(绝对值) |
double(双精度浮点型) | 8 | 2.22507×10^(-308)~1.79769×10^(308)(绝对值) |
long double(长双精度浮点型) | 12 | 2.22507×10^(-308)~1.79769×10^(308)(绝对值) |
以上就是我这次分享的内容啦!写的不好,请多担待!