数据在内存中的存储
Posted 正义的伙伴啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据在内存中的存储相关的知识,希望对你有一定的参考价值。
数据类型是我们在c语言里面最长用到的,每次声明变量时我们其实都使用到了数据类型,但是不同的数据类型在计算机内存中的存储是否相同?以什么样的形式存储?
常见的数据类型也是最基本的类型:
char //字符数据类型
short //短整型
int //整型
long //长整形
long long //更长的整型
float //单精度浮点型
double //双精度浮点型
这里注意一下char也算在整型里面,因为char类型在内存中是以ASCII码形式存储的,算作整型
那么类型的意义是什么?
1.使用这个类型所开辟的空间的大小;
2.如何看待内存空间的视角(指针)
类型的基本归类
整型在内存中的存储
数据在内存中是以二进制的形式存储的!
int a=10;
int b=-10;
要想知道二进制码是如何存储我们要先了解原码、补码、反码:
首先我们要搞清楚三种表示方法均有符号位和数值位,符号位用0表示正,用1表示负
三种方法的转换方法如下
- 对于整型来说内存中存储的其实是补码。
- 计算机在计算时也是按照补码的形式进行运算
为什么?
因为计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(cpu只有加法器)此外,原码与补码相互转换运算过程是相同的,不需要额外的硬件电路。
字节序-大小端介绍
我们看上面这个程序,我们定义了一个变量a,对程序进行调试并监视a的内存,我们得到a的内存为 0a 00 00 00,我们知道变量在内存中是以二进制存储,但是我们这里是以十六进制显示,如图
我们按照以前的认知先写下变量a的二进制:
int a=10 -------- 0000 0000 0000 0000 0000 0000 0000 1010
转换为十六进制 0 0 0 0 0 0 0 a(每四个二进制对应一个十六进制)
我们得到的是00 00 00 0a与地址中存储的不太一样 这就涉及到大小端存储了。
其实大小端存储是一个很简单的问题,一个int是四个字节,内存中的最小单元是一个字节,要把这个4字节的int放到内存中,我们就要先开辟一个四个字节的空间,如图红方框所示,那么问题来了,四个字节(00 00 00 0a)要以什么顺序存入 这块空间。于是计算机以内存的高低为基准规定
小段存储:数据的低位保存在内存的高地址中,而数据的高位保存在在内存的低地址中
大端存储 :数据的高位保存在内存的高地址中,而数据的低位保存在在内存的低地址中
00 00 00 0a中0a是数据的低位(最右边的位,高位是最左边的位)存储到了内存的低位,于是我们判断这是小段存储模式。
大小端实际上是对字节序的排列方法
如何判断字节序
#include<stdio.h>
int main()
{
int a = 1;
char* p = (char*)&a; //强制类型转换
if (*p == 1)
printf("小端存储");
if (*p == 0)
printf("大端存储");
return 0;
}
这里利用指针类型char一次只能访问一个字节的特性完成判断。
关于signed和unsigned整型的表达式运算
- char类型到底使signed char还是unsigned char在c语言中并没有规定,取决于编译器。
- int 和short 规定为signed int。
有符号与无符号整数的整型提升问题
#include<stdio.h>
int main()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf("a=%d,b=%d,c=%d", a, b, c);
return 0;
}
我们直接写出二进制序列:
a=b=c: 11111111
但在执行到 %d 的时候要发生整型提升,由转型提升的规则可知:有符号位的补符号位,无符号为的补零,于是二进制序列就发生了变化
c :00000000000000000000000011111111
a=b:11111111111111111111111111111111
又因为以%d的形式打印,所以结果是 -1 -1 255
注意:%d %u都是打印整型类型,顾打印时要发生整型提升!
#include<stdio.h>
int main()
{
char a = 128;
char b = -128;
pritnf("%u,%u",a, b);
return 0;
}
我们就得到二进制位:
a=b: 10000000;
%u打印时发生整型提升:
11111111111111111111111110000000
故结果是:4294967168,4294967168
其实我们在这里发现了一个规律,%d和%u是最终决定对二进制序列是按有符号还是无符号读取,也就是决定了读取方式,和打印的变量是signed或unsigned无关。这里signed char和unsigned char符号位只在转型提升中有用。
下面我们来探究一下signed char数值随着二进制增大的规律
下图二进制从零逐渐增加,char的值的变化:
signed char有一位是符号位实际上决定大小只有七位,但是signed char和unsigned char的区间长度都是一样的都为28 ,需要注意的是上一题char 128和char -128实际上是同一个数,所以整个char是一个周期,大小始终是在-128到127之间,如正弦函数一样。
#include<stdio.h>
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
这里a[i]从-1开始减小,按照上图的循环减小到-128时下一个数时127 126…1 0 -1 …
但要注意的是 0对应的ASCII码为’\\0’顾strlen到0的时候就会停止。
signed与unsigned类型的大小比较
无符号int 类型 与 有符号int类型:
#include<stdio.h>
int main()
{
int a = -1;
printf("%d", a > sizeof(a));
return 0;
}
这个程序的结果:如果a大于sizeof(a)表达式为真返回并打印1,反之返回并打印0;
已知a=-1 , sizeof(a)=4所以a<sizeof(a), 所以答案为0。其实答案并不是0!
实际上答案为1,这代表着a>sizeof(a),这是为什么呢?
这里sizeof其实返回的是unsigned int的值,而a是一个int值,**int和unsigned int比较时有符号数要转换成无符号数!**所以a的二进制码为32个1,也就是232 -1肯定比4大,所以返回的是1
无符号int 和 非int无符号 类型
#include<stdio.h>
int main()
{
char a = -1;
unsigned int b = 1;
printf("%d", a > b);
return 0;
}
这里a>b时必然会发生整型提升!a从11111111提升为1111111111111111111111111111(32个1),又因为b为unsigned int类型所以a会被转换成232 -1,所以结果为1;
有符号int类型 和 非int无符号类型
#include<stdio.h>
int main()
{
unsigned char c = -1;
int d = -1;
printf("%d\\n", c > d);
return 0;
}
这里char先发生整型提升,转换成signed int类型进行比较!答案为1
不含int类型
处理方法时转型提升转换成有符号int类型进行比较!
规律
一旦出现unsigned int类型一律转换成无符号数进行比较,如果没由出现unsigned int一律转换成有符号int进行比较!
浮点型在内存中的存储
常见的浮点数:
float 、double 、long double
浮点数存储的例子:
#include<stdio.h>
int main()
{
int a = 9;
float* p = (float*)&a;
printf("%d\\n", a); //以整型的视角存储
printf("%f\\n", *p); //以浮点数的视角读取
*p = 9.0; //以浮点型的视角存储
printf("%d\\n", a); //以整型的视角读取
printf("%f\\n", *p);
return 0;
}
这是我们输出的结果,已知float与int一样也是四个字节,但为什么输出的结果不一样?
实际上储存和翻译在内存中的二进制码,既可以解释为整型,也可以解释成浮点型,这取决于他们被使用的方式。如果使用的是整型的算数指令就会被解释为整数。如果使用的是浮点型指令,他就是个浮点数。
总结为一句话:浮点数和整数在内存中存储,读取方式都不同!
浮点数在计算机内部的表示形式:
根据国际标准IEEE754,任意一个二进制浮点数v可以表示成下面的形式:
- (-1)S *M *xE
- (-1)S 表示符号位,当S=0,V为正数;当S=1,V为负数
- M表示有效数字,大于等于1,小于2。
- 2E 表示指数位
例如:float 5.0 二进制位: 101.0=1.010*22
S=0 M=1.010 E=2;
对于32位浮点数,最高位的1位是符号位是,接着的8位是指数E,剩下的23位是M
IEEE754对E和M还有一些要求:
- 在上面M始终是1.xxxxxxxxxxxx的形式,但是保存到23位中可以将“.”前面的1省略,例如1.01就保存01后面补0。
这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M的只有23位,如果舍去1,就可以保存24位有效数字。 - 对于E:因为E是一个无符号的数这意味着,如果E为8位,它的取值范围0-255;如果E为11位,它的取值范围为0-2047.但是我们知道,科学计数法中E可能是负数的,所以IEEE754规定E先加上一个中间数(32位为127,64位为1023)。
然后E内存中取出还可以再分成三种情况
- E补全为1或全为0,这时就逆序按照上面的方法,即指数E减去127,得到真实值,再将有效数字M前加上第一位1;
- E全为0这时如果我们还按照上面的做法,会发现E减去127后是一个非常小的数,有效数字M不再加上第一位1,而是还原位0.xxxxxxxxxxxx的小数。
- E全为1这时,如果有效数字M全为0,表示正/负无穷大。
#include<stdio.h>
int main()
{
int a = 9;
float* p = (float*)&a;
printf("%d\\n", a); //以整型的视角存储
printf("%f\\n", *p); //以浮点数的视角读取
*p = 9.0; //以浮点型的视角存储
printf("%d\\n", a); //以整型的视角读取
printf("%f\\n", *p);
return 0;
再看上面的代码块:
第一部分:按int的视角存储
内存二进制位: 00000000000000000000000000001001
按float的视角去看这个二进制:
0 00000000 00000000000000000001001
S=0 E=-127 M=00000000000000000001001
因为E全为0,顾打印结果位0.0
第二部分按float视角存储
9.0 -> 1001.0 -> 1.001*23
S=0 M=1.001 E=3+127=130
0 10000010 00100000000000000000000
以int类型的视角去读取:1091567616
以上是关于数据在内存中的存储的主要内容,如果未能解决你的问题,请参考以下文章