数据在内存中的存储

前言

        自从学习指针开始,内存这一个概念就经常使用,之前,只考虑数据存储的地址,却没有想过数据到底是怎样存储到内存当中的,又是如何被读取来应用的?本篇就来学习,理解数据在内存中到底是如何去存储的。

目录

一、整型数据在内存中存储

二、大小端字节序

大端字节序存储:

小端字节序存储:

三、试题练习

练习1、

练习2、

练习3、

练习4、

练习5、

四、浮点型数据在内存中存储


一、整型数据在内存中存储

在学习计算机基础时,就接触过整型的二进制表示:原码,反码,补码

对于有符号的整数,这三种表示方式的有符号位数值位符号位用0表示,用1表示,用二进制最高位来表示符号位,其他都是数值位。

对于正整数来说:原码,反码和补码都相同

负整数三种表示方法各不相同

        原码:直接将整数按照正负转换为二进制得到的就是原码

        反码:将原码的符号位不变,其他位依次按位取反就得到反码

        补码:反码+1就是补码

反码与补码之间的转换就是,取反加一

对于整型数据来说:数据就是以二进制补码的形式存放在内存中

        在计算机系统中,数值一律用补码来存储和表示。原因就在于,使用补码可以将符号位与数值位统一处理。

        同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

二、大小端字节序

第一次看到这个大小端字节序,可能感到很疑惑,具体什么是大小端字节序呢?

这里,我们写代码调试来看一下:

int main(){
    int a = 0x11223344;

    return 0;
}

这里我们可以看到a当中0x11223344这个数字是以字节为单位,倒着存储的,这又是为什么呢?

        什么是大小端?

我们知道,在内存中存储数据一般是以字节为单位的,而当超过一个字节大小的数据,存储的过程中就要遇到顺序问题,所以,内存中存储数据是有一定顺序的,按照不同的存储顺序,就分为大端字节序存储小端字节序存储,具体概念如下:

大端字节序存储:

是指数据的 低位字节 内容保存在 内存的高地址 处,而数据的 位字节内容,保存在内存的 地址处。

小端字节序存储:

是指数据的 低位字节 内容保存在 内存的低地址 处,而数据的 位字节内容,保存在内存的 地址处。

为什么要有大小端字节序呢?

        这是因为在计算机系统中,是以字节为单位的,每个地址单元都对应一个字节,一个字节是8个bit位,但是在计算机中,有8bit的char型,有16bit的short类型,还有32个bit的long型(具体大小取决于编译器);此外,对于那些位数大于8位的处理器,如16或者32位处理器(还有64位等),它们的寄存器宽度都要大于一个字节,这样就必然存在着如何处理多个字节安排这一个问题。因此就有了大端存储模式和小端存储模式。

我们了解了大小端字节序存储这个东西,接下来就写代码来判断一下VS编译器是大端存储模式还是小端存储模式呢?

int check_sys()
{
	int i = 1;
	return (*(char*)&i);
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

这里 *(char*)&i将整型指针强制类型转化成char*再解引用就只访问一个字节,(访问的就是地址低的那一个字节)这样如果访问到的是1,就说明将int将低位字节存储到低地址处,存储方式就是小段存储方式;如果访问到的不是1,就说明存储方式是大端存储方式。

当然,也有其他的方式来判断:

int check_sys()
{
     union
     {
         int i;
         char c;
     }un;
     un.i = 1;
     return un.c;
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

这里用到了自定义类型的联合体这里简单说一下联合体(后面会详细讲解):
        联合体也叫做共用体,联合体可以由多个成员不同类型构成,联合体特点就是所以的成员共用一块内存,(编译器只会给最大的成员分配足够的内存),给联合体的其中一个成员赋值,其他成员的值也会发生变化。

        这里也是,给int类型的i进行赋值,然后用char类型去访问,只访问一个字节。

三、试题练习

接下来,看一些例题,来加强一下理解

练习1、

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;
}

看到这里,应该要知道一个问题,字符char类型在内存中占一个字节,在内存中也是以二进制的方式存储的,例如a,b等这些存储的其实是对应的ASSIC码值。

接下来,看一下代码,char a = -1;char一个字节,8个bit位,整型存储的是二进制,所以将-1转化位二进制就是 :          

10000001 ——原码

11111110 ——反码

11111111 ——补码

将补码存储到a当中

补充:char默认就是有符号型(signed char)

这里signed char与char存储方式一样

再看 unsigned char c = -1;这里还是将-1转化位二进制

11111111 ——补码

将补码存储到c当中去,而c是无符号类型,它就会把符号位当成数值位来看待;

接下来以%d的形式输出,由于char只占一个字节,这里就会涉及到整型提升将char类型提升至int型:

整型提升知识补充:

整型提升的规则:
        整型提升分为有符号和无符号两种,对于有符号的:整型提升时是按照变量的补码被截断时的最高位(符号位)是什么进行补位的,如果截断后最高位即最左面的一位数(符号位)为 1 则在最高位前补 1 ,如果最高位(符号位)是 0 则在前面补 0 ,补够32位即int类型即可。 无符号的: 直接在被截断的前面补 0 即可。

简单了解一下整型提升,再来接着看代码

以%d的形式输出,首先就要先整型提升

11111111 ——a

11111111 11111111 11111111 11111111 —— (补码)整型提升以后的a

10000000 00000000 00000000 00000000 —— 反码

10000000 00000000 00000000 00000001 —— 原码

然后进行输出,因为是%d是以有符号整型输出,所以这里输出的就是 -1;

b与a一样,都位有符号类型。

再看无符号型的c,

11111111 —— c

00000000 00000000 00000000 11111111 —— (补码)整型提升以后的c

00000000 00000000 00000000 11111111 —— 反码

00000000 00000000 00000000 11111111 ——原码

因为c是无符号类型(即是正数)原反补码都相同

然后以%d形式进行输出,输出结果就是255。

练习2、

#include <stdio.h>
int main()
{
     char a = -128;
     printf("%u\n",a);
     return 0;
}

char 有符号类型 -128转化位二进制

10000000 —— 原码

10000000 —— 反码

10000000 —— 补码

a里存储的就是-128的补码;

然后以%u(无符号整型)输出,首先还是要整型提升 -128是有符号类型

10000001 —— a

11111111 11111111 11111111 10000000 —— 整型提升后

因为是以有符号类型输出,所以编译器就会将整型提升后的符号位当作数值来看待,直接将其转化成十进制,然后输出,结果就是4294967168

练习3、

#include <stdio.h>
int main()
{
     char a = 128;
     printf("%u\n",a);
     return 0;
}

练习2中是将-128存储,现在来存储128然后以%u形式输出。

这里 char类型取值范围 -128 —127

128存储到char类型中,可能会出现数据丢失的现象

这里  10000000 —— a

然后整型提升,符号位是0

00000000 00000000 00000000 10000000 —— 整型提升后(补码)

11111111 11111111 11111111 01111111 —— 反码

11111111 11111111 11111111 10000000 —— 原码

以%u形式输出,将符号位当成数值位来看待,直接转化成十进制数然后输出,结果就是4294967168

练习4、

#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;
}

在第一眼看到这个代码时,第一感觉可能会跟我一样,一看循环进行赋值了1000,那用strlen来求字符串长度,那不就是1000吗?

在讲述这一个练习之前先补充一点知识

知识补充:

        我们知道,char类型只占用一个字节,也就是8个bit位,那再存储数据的时候,它所存储的数据就是有限制的

对于有符号的char类型

图中我们可以看到,127再加一后  0111 1111 就变成了 1000 0000;

而1111 1111加一就变成了 0000 0000

对于无符号类型的char就简单了

这里  1111 1111加一就变成了  0000 0000。

知道了char类型的取值范围再看这个练习题

a[i] = -1 - i ;

i从零开始,a[0]就等于-1,这样一直赋值下去,当 (-1 - i )等于-128 时,i++;这时表达式的值就变成了127,再接着进行赋值,这个表达式的值总会变成0,而我们又知道strlen遇到'\0'停止,'\0'的ASSIC码对应的值就时0,所以,strlen遇到a这个数组中的0时,就停止计数了,所以这里输出的结果是255。

练习5、

在有了以上对char类型数据的理解,看以下两个题就容易多了

#include <stdio.h>
unsigned char i = 0;
int main()
{
     for(i = 0;i<=255;i++)
     {
     printf("hello world\n");
     }
     return 0;
}

这个代码会一直循环输出 hello world  , unsignned char取值范围是 0 -- 255, 255再加一就变成0,循环会一直运行,如果使用VS编译器,代码会有错误它提示

int main()
{ 
    unsigned int i;
    for(i = 9; i >= 0; i--)
    {
        printf("%u\n",i);
    }
    return 0;
}

这个也是同理,unsigned char 类型  0再减去1 就变成255,循环一直进行下去

四、浮点型数据在内存中存储

了解了整型数据在内存中的存储,接下来,来了解浮点型数据在内存中的存储:

浮点型数据的存储,根据国际标准IEEE(电器和电子工程协会)754,任意一个浮点数V都可以表示成一下形式:

  • 这里-1的S次方表示符号位,S = 0,V就是正数;S=1,V就是负数
  • M表示有效数字,M是大于等于1,小于等于2的
  • 2的E次方表示指数位

举例:
十进制的5.0,写成二进制 101.0 ,相当于1.01*2^2

用以上格式表示就是 S=0,M=1.01,E=2。

浮点型数据的存储

对于32位的浮点型数据,最高位存符号位S,接着8位存指数E,剩下的23位存储有效数字M。

而对于64位的浮点型数据,最高位存符号位S,接着11位存指数E,剩下的的52位存有效数字M。

知道是怎样存进内存的,那也要知道在存储过程中,存储的规定

IEEE 754 对于有效数字M和指数E,还有一些规定

对于有效数字E:

有效数字   1<=M<2,也就是M可以写成 1.xxxxx的形式,其中xxxxx是小数部分。

IEEE 754 规定,在计算机内存保存M时,默认这个数的第一位总是1,所以可以将其舍去,只保存后面的小鼠部分;

        比如,在保存1.01时,只保存01,在读取的时候,再把第一位的1加上去。这样做,是省略1位有效数字。比如,32位浮点数,留给M只有23位 ,舍去第一位的1,就相当于可以保存24位有效数字

对于指数E,存储的情况就比较复杂

首先,E是一个无符号整数,这就说明,如果E是8位,它的取值就是 0-255;如果E是11位,取值就是0-2047.

但是,我们知道指数是存在负数的,所以IEEE 754 就规定了在存储时E的真实值必须加上一个中间数,(8位的E,中间数是127; 11位的E,这个中间数就是1023),比如 2^10的E是10 ,保存成32位浮点数,在存储时就必须保存成 10+127 = 137,就是 1000 1001。

浮点型数据的数取

大致可以分为三种情况:

1、E不全为0或不全为1

        这时,浮点数采用下面规则表示,(E的计算值减去127或1023),得到真实值,在将有效数字M前加上第一位的1.

        比如:0.5二进制是0.1,根据规定正数部分必须是1,所以就表示位 1.0^2*(-1),其阶码(E的存储值)是 -1+127 = 126,表示为  0111 1110,位数1.0 去掉整数部分为0,补齐23位 ,所以二进制表示形式:

 0 01111110 00000000000000000000000

2、E全为0

这时,浮点型的指数E就是1-127(或者1-1023)即为真实值,有效数字M不在加上第一位的1,而是还原为0.xxxxxx的小数。(这是为了表示+/- 0,和接近于0的很小的数字)

3、E全为1

这时,如果有效数字M全为0,就表示+/- 无穷大(正负取决于符号位s)

了解了这些,就看一段代码:

#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;
}

在之前看到这样的运行结果,可能会感到很懵,在我们知道了浮点型数据的存储以后,来看这样的代码:

        首先第一个 printf("n的值为:%d\n",n); 这个应该都可以理解,n是整型数据,以%d形式输出,就是9;

        然后来看 printf("*pFloat的值为:%f\n", *pFloat);

9 以整型的形式存储在内存中,二进制如下:

0000 0000 0000 0000 0000 0000 0000 1001

要以%f 形式输出,那就会把这个二进制序列按照浮点型数据二进制来处理,

符号位S = 0 ,后面8位即指数位 E = 0000 0000,

最后的23位就是有效数字 M= 000 0000 0000 0000 0000 1001。

这样指数E全为0,就按照指数E全为0 的方式,

浮点数 V= (-1)^0 *000 0000 0000 0000 0000 1001 *2^(-126);

这样,V显然就是应该很小的接近于0的正数,用十进制小数表示就是0.000000

        紧接着来看 *ploat = 9.0 以后,在以%d形式输出:

这里就要按照浮点型数据存储将9.0存储到内存中,

9 的二进制 1001.0 换成科学计数法就是 1.001 * 2^3

所以S = 0,E = 3 + 127, M等于001后面追加20个0  , 写成二进制就是

0 10000010 001 0000 0000 0000 0000

这个二进制数,被当作正数来解析时,就被当中内存中的补码,原码 转换为十进制就是 1091567616

        最后以%f的形式在输出以浮点型存储到内存中的9.0,输出结果就是 9.000000。

感谢观看,希望一下内容对你有所帮助,如果内容对你有作用,可以一键三连加关注,作者也正在学习中,有错误的地方还请指出,感谢!!!
  • 28
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值