C语言数据存储
Zoroxs 人气:0内存简单介绍
大家肯定经常听说内存这个词,内存到底是什么呢? 在计算机中,进程都要加载进内存中,也是我们各种数据的流通途径,C语言中,大家肯定都知道指针变量,指针变量中保存的就是内存的地址,那么,什么是内存的地址呢?
内存的单位是字节
对于32位的机器,有32根地址线,每根地址线在寻址时,产生的高低电压分别为0/1,那么32根地址线产生的地址就会是
00000000000000000000000000000000
00000000000000000000000000000001
00000000000000000000000000000010
…
11111111111111111111111111111111
这里就有2^32次方个地址
大家应该知道,还有64位的机器,64根地址线又有多少个地址呢,大家可以计算一下
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,
所以一个指针变量的大小就应该是4个字节。 那如果在64位机器上,有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
这串编号就是内存单元的地址,就像酒店的房间号一样,对应着内存中的一个字节大小的房间
我们在vs中来看一下
这里是以十六进制的方式展示的,大家也知道,32个数字看起来太长了。
整数与字符在内存中的存储
关于c语言中的数据类型,大家在写了这么多代码后肯定也很清楚了,C语言中有整型、浮点型、字符型、等等
我们来研究一下整数在内存中是如何存储的
大家都知道,定义变量,会在内存中开辟空间来存储
int a = 20;
int类型在vs中占据4个字节的空间,那么如何存储呢?
这就涉及到原码反码补码的概念
- 计算机中的整数有三种2进制表示方法,即原码、反码和补码。
- 三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,剩下数值位
- 正数的原、反、补码都相同。
- 负整数的三种表示方法各不相同。
原码:
直接将数值按照正负数的形式翻译成二进制就可以得到原码
反码:
将原码的符号位不变,其他位依次按位取反就可以得到反码
补码:
反码+1就得到补码。
对于整型变量来说,内存中存放的其实是补码
使用补码,可以将符号位和数值域统一处理,加法和减法也可以同一处理,因为CPU只有加法器
eg:
int a = 20; //原码 : 直接写二进制 00000000000000000000000000010100 //反码--补码 -- 正数的原反补相同 int b = -5; //原码: 10000000000000000000000000000101 //反码:符号位不变,按位取反 // 11111111111111111111111111111010 //补码:反码+1 // 11111111111111111111111111111011 -- -5的补码
上边可以看见-5的补码是11111111111111111111111111111011 ,我们如何确认呢?
转换成16进制为fffffffb
大家可以看到确实是使用补码存储的,但是为什么是倒着存储的,后边再来说,这是由于大小端的问题
给大家举几个例子,不知道存储无法分析出结果代码
//1. #include <stdio.h> int main() { char a = -128; printf("%u\n",a); //-128 原码: 10000000 00000000 00000000 10000000 //-128 反码: 11111111 11111111 11111111 01111111 //-128 补码: 11111111 11111111 11111111 10000000 //存在a里面的,因为只有一个字节 10000000 //所以会当做无符号整数打印 --整形提升 //提升为 11111111111111111111111110000000 //所以结果是一个很大的整数,大家可以尝试一下 return 0; }
#include <stdio.h> int main() { char a = 128; //128的原码反码补码 : 00000000000000000000000010000000 //存在a里的:10000000 //整形提升 :11111111111111111111111110000000 //所以结果还是那个很大的整数 printf("%u\n",a); return 0;
例子就简单给大家举到这里,大家一定要记住整数在内存中是以补码的形式存储的
字符存储的是ASCII码,所以和整数同
浮点数在内存中的存储
我们来看一下浮点数在内存中的存储
抛砖引玉:
#include <stdio.h> int main() { int n = 9; float *pFloat = (float *)&n; printf("n的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); *pFloat = 9.0; printf("num的值为:%d\n",n); printf("*pFloat的值为:%f\n",*pFloat); return 0; }
上面代码的打印结果到底是什么呢?
是不是非常出乎大家的意料呢,这里就可以看出,浮点数的存储肯定和整数是不同的。那浮点数到底咋存的呢?
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
- (-1)^S * M * 2^E
- (-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
- M表示有效数字,大于等于1,小于2。
- 2^E表示指数位。
看得很迷糊,直接上例子
v = 5.5
= 101.1 --二进制表示
= 1.011 * 2 ^2 – 科学记数法表示
因为是正数 s = 0
m = 1.011
e = 2
IEEE 754规定:
对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
IEEE 754对有效数字M和指数E,还有一些特别规定
1=<M <2 ,所以M可以写成1.xxxxx 所以可以舍去1 ,只存储xxxxxx
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字
对于指数E,E是一个无符号整数,但是科学记数法指数是可以出现负数的,所以
IEEE 754规定了偏移量,如果E为8位,则加上127 ,如果E为11位,则加上1023
我们举个例子
float f = 8.5f;
//二进制 : 1001.1
//科学计数法表示: 1.0011*2^3
//S = 0 M = 1.0011 E = 3
//存储进去的应该是:
0 10000010 00110000000000000000000
//我们可以验证一下
转换成16进制
0100 0001 0001 1000 0000 0000 0000 0000
//41 18 00 00
我们来看一下代码
和我们想的一样
我们再来举一个负数的例子
float t = -3.5f;
//二进制: 11.1
//科学记数法: 1.11*2^1
//S = 1 M = 1.11 E = 1
//存储:
1 10000000 11000000000000000000000
//转换成16进制
1100 0000 0011 0000 0000 0000 0000 0000
c0 60 00 00
那我们从内存中读取出来的二进制位如何解析成浮点数呢
关于E,有三种情况
E不全为0或不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1
2.E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,
有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于 0的很小的数字。
3.E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);
我们来分析一下最开始的题目
浮点数和整数的存储就介绍到这里了,有哪里不清楚的朋友可以私信我
大小端存储模式及简介
上边有一个悬念,为什么是倒序存储的
那什么是大端小端呢?
大端(存储)模式: 数据的低位存储在内存的高地址中,数据的高位存储在内存的低地址中
小端(存储)模式: 数据的低位存储在内存的低地址中,数据的高位存储在内存的高地址中
那为什么会有大小端呢?
内存中以字节为单位,但是比如int 是4个字节,那如何安排这个4个字节呢?就导致了大小端存储模式
例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则 为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式 还是小端模式。
那我们如何测试当前电脑是哪种存储模式呢?
int main(void) { int a = 0x11223344; return 0; }
我们调试打开内存看一下
很明显,数据的低位存储在内存低地址中,所以为小端存储模式。
我们能不能写一个程序,直接告诉我们大小端呢?
我们来分析一下
我们来看一下代码
int decide_byte_orde() { int i = 1; return *(char *)&i; }
我们来测试一下
总结
深入理解数据的存储是非常有必要的,我们之后碰到很多问题都会豁然开朗,大家一定要好好研究一下
加载全部内容