数据的存储

首先先看下面的代码:

int test_num3;
int main()
{
	int test_num1 = 1;
	int test_arr[10] = { 0 };
	float test_num2 = 5.5;

	return 0;
}

上面的代码包含了int类型变量,int类型元素组成的数组,float类型变量的声明和依次开辟内存空间。根据上面的代码,我将依次说明内存中数据的存储方式。

我们声明的变量存储在哪里?

这就要说到内存的布局分区,一般来说,内存分为几个区域:代码区、全局数据区、堆区、栈区。
首先是栈区,是存放所有局部变量的内存区域,所有声明的局部变量空间都是在这里开辟的,一旦出变量作用域后立刻销毁。就是执行函数时分配,函数调用结束时销毁。上面声明的test_num1、test_arr和test_num2都是分配在栈区上的。
然后是堆区,自己用malloc等申请内存空间都是在堆区开辟的,并且销毁的工作都是需要自己手动free完成的,所以一般申请的内存空间返回的地址都必须用指针接收,并且指针的值不得改变。因此在堆区存储数据的生命周期是由我们决定的。
接着是全局数据区,该区域用来存放所有的全局数据,包括常量和变量,以及static修饰的局部变量(其实就是通过挪动到全局数据区来延长变量的生命周期,因为全局数据区的内存是等到程序运行结束后才释放的)还有常量字符串,另外全局数据区的变量若未初始化值,则默认为0,因为在全局数据区中字节默认为0x00。因此该区的存储数据的生命周期是到程序结束才终止的。
最后是代码区,用于储存运行时所需要的我们编写代码的机器码形式,以及define所定义的宏,本质上也是换了个别名的文本段,总之代码区储存的都是文本。
ps:代码区的地址是在内存的高地址,而栈区是位于内存的低地址。
下面主要讨论的是数据在内存中的存储形式。

栈中数据的存储

1.栈中数据与数据之间的相对顺序:
在栈区中,数据结构是依照叠叠乐的形式一层一层分配上去的,先放进去的被压在底端的位于栈中的高地址,晚放上去的顶端的数据位于栈中的低地址。在函数调用中,被调用的函数会分配到一定空间的栈帧(内存中为函数分配的一段空间),随着调用的一层层进行,栈帧越垒越高,调用关系是底下的函数栈帧对应的函数调用上面函数栈帧对应的函数,地址越来越低。
然后我们看向某一函数栈帧的内部,比如上面贴的main函数的对应的栈帧内部。栈帧内部,按照变量在代码中定义的先后顺序,仍然按照栈这一数据结构依次在栈帧中进行分配,由前到后分配到的内存地址由高到低递减,比如上面代码中声明的test_num1、数组test_arr[10]、test_num2按照所在地址,是由高到低排列的。就如我下面粗糙的画图所示:
在这里插入图片描述
由此我们知道到函数与函数之间、数据与数据之间的相对顺序,按照出现的先后,由高到低分配栈中的内存
2.有符号整形数的存储方式(int为例)
说明有符号整形数的存储方式,就是介绍原码、反码、补码的概念,因为有符号整形数在内存中就是以补码的形式存储的
由于正数的原码,反码,补码相同,下面以负数-1为例。
所谓的原码,就是把待编码的数字写成二进制的形式,得到的二进制数再加上符号位就是该数的原码。int类型共占32个bit位,最高位为符号位,1表示负数,0表示正数,其他位用来表示数字位。比如-1的原码为:10000000 00000000 00000000 00000001.
反码就是除了符号位以外,其他位取反。如-1的反码为:11111111 11111111 11111111 11111110
补码就是反码+1。如-1的补码为:11111111 11111111 11111111 11111111。这就是-1在内存中存储的编码。
ps:补码的引入有两个原因:1.不引入补码前,0的原码表示会有两个,引入补码后,不存在这个问题。2.引入补码使得减法也可以变成加法计算。(因为原码情况下负数值逐渐递减,而对应的原码从数字位表达的数值来讲是逐渐增加的,但是原码情况下的正数值逐渐递减,对应的原码从数字位表达的数值来讲却是逐渐减少的,这样子使得正数和负数编码的递增规律不同,因而原码形式无法进行正数和负数的加法。只有变成了补码,这样的递增规律才是相同的)
pps:告诉大家个冷知识:正数补码(或者说原码)和其相反数(负数)的补码相加为2的32次方,意思就是假设正数是a,其负数可以通过(~a+1)取得。具体证明贴图(int类型):在这里插入图片描述
3.数据中的字节序
每个数据表示成二进制编码后再按照字节大小有最低字节与最高字节的部分,数据中的字节序讨论的就是数据的编码中低字节是存储在这个数据所占空间的低地址,还是数据所占空间的高地址。这样说可能有些抽象,比如我们上面初始化的test_num1=1,在int类型编码为0x00 00 00 01,最低字节部分是01,最高字节部分是00,我们看到是这个最低字节到底是储存在test_num1分配到的4字节空间中低地址字节,还是高地址字节。如果数据的低字节部分储存在低地址,我们称为小端存储,如果储存在高地址,我们称为大端存储。按照我上图所画的test_num1中数据所存的样子,此时采取的是小端存储。因为01(十六进制)放在了test_num1所占空间中的低地址位置。
另外值得一提的是,尽管栈中是按照地址从高到低分配内存的,但是在数组中,随着数组下标的增长,数组中的元素地址却是由低到高变化。正如上面粗糙的画图所示。

4.浮点数的存储方式(以float类型为例)
以5.5为例,一般地,我们可以将小数写成二进制科学计数法的形式,比如5.5首先写成二进制:101.1,然后用可以写成(-1)^1 × 1.011 × 2^10(加粗均为二进制)。
每一个小数都可表示成(-1)^X × 1.X × 2^X的形式。
所以float类型的存储中,将第1位作为符号位,用来表示前面的第一个X,如果为0,即表示(-1) ^0,也就是存的是正数,如果是1,即表示(-1) ^1,也就是存的是负数。上面的5.5第一位存的便是0。
第2-9位,也就是8个bit位,来储存指数上的数字。即上面一般形式的第三个X的数字。但是由于要表示负数,而8位所能表示的数字范围为0-255,没有办法表示负数,于是就规定折中,一半的范围表示负数,一半的范围表示正数。具体的规则是将实际的指数上的数字加上127后的8位二进制数存储在第2-9位中,然后取出来的时候自动减去127为实际的指数部分,这样存储在内存中的这8位上的数字虽然是0-255,但是实际上表示的指数范围是(-127)-128。如上面的5.5后面写成的一般形式所示,指数要加上127,就由00000010变成10000001,这便是5.5在float类型的二进制编码的前2-9位二进制数。
第10-32位,也就是23个bit位,用来存储指数前的数字。也就是上面一般形式中的第二个加粗的X。由于每个小数表示成二进制的科学计数法时,指数前的数字都可以写成1.XXX的形式,所以在存储这部分的数字时,就不将前面的1.储存进去了,只存小数点后面的23位二进制数,需要的时候取出来自动前面补上1.就可以了。比如上面5.5指数前的数字为1.011,在5.5的float类型表示的二进制编码的10-32位就是0110 0000 0000 0000 0000 000(总共23位)。
结合上面所说,5.5在float类型的编码规则下的二进制编码就是:0 100 00001 01100000000000000000000,写成十六进制就是0x40 b0 00 00(两位为一个字节,故用空格隔开),如果是小段存储,就是像上面粗糙的内存图所示了。或者如下图,在vs2013下调试时显示test_num2在内存中的表示:
在这里插入图片描述
总结起来就是下面这幅图:
这是float类型的存储
这是double类型的存储,本质上和float类型一致,不过是空间大小扩大,表示的精度变大罢了
还有几个特殊情况需要注意:
1.当2-9位(或者double1-12位),也就是上面的E区域中的所有位都为0时,此时M区域(也就是指数前面的数字)取出来时不补上1.,而是补上0.,并且E中的数字(即指数位的数字)取出来不是减去127,而是减去127再加1,也就是指数位为-126(或者是-1022)。比如假设float在内存中的编码为:0 00000000 11110000111100001111000,那么读出来认为储存的这个数字真实值为:0.11110000111100001111000 × 2^(-126),也就是很小的数字。
2.当2-9位(或者double1-12位),也就是上面E区域中所有位都是1时,读取出来认为是正负无穷大(取决于符号位s)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值