数据在内存中的存储

简介: 数据在内存中的存储

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

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

对于有符号的整数,这三种表示方式的有符号位数值位符号位用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。

感谢观看,希望一下内容对你有所帮助,如果内容对你有作用,可以一键三连加关注,作者也正在学习中,有错误的地方还请指出,感谢!!!

相关文章
|
24天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
16天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
20天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2577 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
18天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
3天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
2天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
163 2
|
20天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1576 16
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
|
22天前
|
编解码 JSON 自然语言处理
通义千问重磅开源Qwen2.5,性能超越Llama
击败Meta,阿里Qwen2.5再登全球开源大模型王座
977 14
|
4天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
221 2
|
17天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
734 9