C语言中数据如何存储进内存揭秘
作者:Zoroxs
内存简单介绍
大家肯定经常听说内存这个词,内存到底是什么呢? 在计算机中,进程都要加载进内存中,也是我们各种数据的流通途径,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; }
我们来测试一下
总结
深入理解数据的存储是非常有必要的,我们之后碰到很多问题都会豁然开朗,大家一定要好好研究一下
到此这篇关于C语言中数据如何存储揭秘的文章就介绍到这了,更多相关C语言数据存储内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!