当前位置: 首页 > news >正文

数据在内存的存储

整数在内存中的存储

       我们来回顾一下,整数在计算机是以补码的形式进行存储的,整数分为正整数和负整数,正整数的原码、反码和补码是一样的,负整数的原码、反码和补码略有不同(反码是原码除符号位,其他位按位取反(即0变成1,1变成0),补码就是反码+1),负整数的补码变为原码也是一样的,保留符号位,其他位按位取反,再+1就可以得到原码。
       在计算机储存数据的时候,会有一个符号位(最开头的数位用1来表示负数,用0来表示正数)来表示正负,剩下的是数值位来表示数值。正数的原码、反码和补码是完全一样的,这里注意符号位是不会变的,负数的反码就是除了原码的符号位不变之外,其他位按位取反(就是0变成1,1变成0),补码就是在反码的基础上加1
这里我们以32位机器来举例:

大小端字节序

       我们来引入一个例子

大家是不是看到存储的数据是倒过来的,当数据超过一个字节的时候就会出现字节存储的顺序问题,这就是大小端字节序存储的问题。

解释

大端字节序就是数据的高位存到低地址处,低位存到高地址处。
小端字节序就是数据的低位存到低地址处,高位存到高地址处。

我们以从左到右是从低地址到高地址进行增加为例:

由此可见,开始我们引入的例子是按小端存储的,我是用VS2022来演示的。

练习

问题一

设计代码来判断当前机器的字节序

我们可以使用一个数字来进行判断,这里我使用数字1,数字1的十六进制表示为0x00000001,我们只要拿出第一个字节就可以判断大小端存储了,如果拿出的是0,就是大端字节序存储,如果拿出的是1,那就是小端字节序存储。

#include <stdio.h>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;
}

代码分析题

代码一
#include <stdio.h>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有没有符号取决于编译器的,我这里使用的是VS2022,char是有符号的,所以a和b 就会打印-1,但是c 就不一样,首先-1在内存中的原码是10000001,反码是11111110,补码是11111111,由于 c 是无符号的,所以在内存中的11111111的第一个1就不会解读为符号位,而是正常的数位,所以打印255。

来看一下运行结果:

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

首先 -128 写出二进制为1000 0000 0000 0000 0000 0000 1000 0000,反码为1111 1111 1111 1111 1111 1111 0111 1111,补码为1111 1111 1111 1111 1111 1111 1000 0000,由于是char 类型,所以发生截断,以八个比特位来进行保存,就是1000 0000,以%u的形式打印,%u是无符号的整型(就是unsigned int 类型),所以发生整型提升,就变成1111 1111 1111 1111 1111 1111 1000 0000,这个数字我们拿计算机算一下,就会得出

来看一下运行结果:

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

128 的原码是 0000 0000 0000 0000 0000 0000 1000 0000,由于是整数,所以原码反码补码是一样的。由于是char 类型发生截断,保留八个比特位,就是1000 0000,用%u来进行打印,发生整型提升,由于char 是有符号的,所以提升为1111 1111 1111 1111 1111 1111 1000 0000 ,然后打印,由于是无符号的整数打印,其实结果和上一道题目的结果是一样的。

代码四
#include <stdio.h>
#include <string.h>int main()
{char a[1000];int i;for (i = 0; i < 1000; i++){a[i] = -1 - i;}printf("%d", strlen(a));return 0;
}

这个我们就要思考 char 是有取值范围的,-128~127,一共有256个数字,strlen遇到\0,就会停止计算,这里的a从-1开始存储,当a变为-128时,再减一就会变成127,一直减到0,之后减一变为-1,依次类推形成一个循环结构,就如下图所示,所以,strlen(a)的计算结果应该是0之前的255个数。

来验证一下:

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

这个代码还是一样,拿出我们的循环图:

当 i 从 1 开始,加到255时,再加1 就不是256,而是255,所以不会跳出循环,而是一个死循环。

代码六
#include <stdio.h>int main()
{unsigned int i;for (i = 9; i >= 0; i--){printf("%u\n", i);}return 0;
}

这个代码其实不用画循环图也能做,因为unsigned int 是无符号的整数,i 永远大于等于0,所以这还是一个死循环。

代码七
#include <stdio.h>//x86环境,小端字节序存储int main()
{int a[4] = { 1, 2, 3, 4 };int* ptr1 = (int*)(&a + 1);int* ptr2 = (int*)((int)a + 1);printf("%x,%x", ptr1[-1], *ptr2);return 0;
}

先看ptr1:

&a取出的是整个数组的地址,&a+1是跳过整个数组,ptr1[-1] = (ptr+(-1)),由于prt1是int类型的,所以ptr1-1就会往后移动一个int,解引用就是指向 4

ptr2:
由于是小端字节序存储,我们来画一下内存图:

由于a 是数组首元素的地址,所以 a 其实指向 1 ,由于(int) a 强制类型将a转换为int类型,+1后就是a的地址+1,指向01后面的00,由于ptr2是int*类型,搜易解引用就会取出四个字节的数据,也就是0x02 00 00 00

浮点数在内存中的存储

       首先我们要知道浮点数的存储和整数的存储是不一样的,不信的可以看一下下面的运行截图:

现在我们来探讨一下浮点数在内存中是如何存储的:
由于浮点数的存储比较复杂,为了方便大家快速了解,我对此进行了一下总结:
首先,一个公式:(-1)^S * M * 2^ E , 浮点数也是有符号的,所以我们用S(二进制位,所以S只能取 0 或 1)来控制(-1)的0或1次方进而也就控制了浮点数的正负性,然后就是我们将浮点数化成类似科学计数法的形式(在二进制里,我们化成1.xxxx*2^E), 之后我们去掉1留下的来的xxxxx就是M,而E的处理就有点复杂,因为E可能为负数,所以为了避免负数的情况我们将其加上一个中间数(127或者1023)

注意了:浮点数的二进制表示,如0.5,怎么表示小数部分,要注意小数点后面浮点数的二进制权重是从2^ (-1)、 2^ (-2)、2^ (-3)…依次叠加的,所以我们要凑出0.5,也就是1,因为1*2^(-1)就是0.5

我们来看一下32位的float和64位的double存储情形:

E加中间数也是根据浮点数的类型进行加的,32的float加127,64位的double 加1023

正常情况下:

E不为全0或全1,那就是正常处理,减去中间数,取出S得到正负,取出M再加1得到一个1.xxxx,然后通过公式(-1)^S * M * 2^E

特殊情况:

当E位全0:
当E为全0的时候,在float下,E就是-127;在 double 下,E就是-1023,那么2^E就是一个很小的数,接近0

当E为全1:
当E为全1时,E就是一个很大的数,2^E就会使这个浮点数接近无穷大。

现在我们来分析一下开头的例子:

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

9 的原码是0000 0000 0000 0000 0000 0000 0000 1001
32位的float,S是0,E是取八位 0000 0000 ,M是000 0000 0000 0000 0000 1001,由于E为全0,所以浮点数无限接近0。

9.0 用浮点数的公式表示:先算出M来,9.0 转化为二进制 1001.0,变为科学计数法就是1.0010 * 2^3 ,则M 为001,后面凑0,知道凑满23位,E等于3+127=130,化成二进制就是
1000 0010 ,由于是整数,所以符号位取0。

所以在内存中9.0表示为:
0100 0001 0001 0000 0000 0000 0000 0000

由于要进行整数打印,将上面一串二进制直接翻译成整数,拿计算机一算:

结果就是我们打印的结果。

http://www.lryc.cn/news/322788.html

相关文章:

  • JavaScript之ES中的类继承与Promise
  • ​浅析多模态大模型技术路线梳理
  • 使用 Amazon SageMaker 微调 Llama 2 模型
  • 牛客小白月赛86(D剪纸游戏)
  • MySQL的基础操作与管理
  • Pytorch 中的forward 函数内部原理
  • 四、C语言中的数组:如何输入与输出二维数组(数组,完)
  • 基于python+vue智慧农业小程序flask-django-php-nodejs
  • 好用的GPTs:指定主题搜索、爬虫、数据清洗、数据分析自动化
  • 使用Qt自带windeployqt打包QML的exe
  • C代码快速傅里叶变换-分类和推理-常微分和偏微分方程
  • 计算机组成原理 双端口存储器原理实验
  • [音视频学习笔记]六、自制音视频播放器Part1 -新版本ffmpeg,Qt +VS2022,都什么年代了还在写传统播放器?
  • GPT-5可能会在今年夏天作为对ChatGPT的“实质性改进”而到来
  • 官宣|阿里巴巴捐赠的 Flink CDC 项目正式加入 Apache 基金会
  • 部署单节点k8s并允许master节点调度pod
  • Django日志(三)
  • 【吾爱破解】Android初级题(二)的解题思路 _
  • 富格林:谨记可信计策安全做单
  • 【工具使用】mingw64编译完成运行可执行文件时出现乱码
  • WebSocket 使用示例,后台为nodejs
  • 【算法】力扣【树形DP】687. 最长同值路径
  • S32DS用PE调试报错
  • Day02-DDLDMLDQL(定义,操作,查询)(联合查询,子查询,字符集和校对集,MySQL5.7乱码问题)
  • 3D高斯泼溅的崛起
  • 基于python+vue家政服务系统flask-django-php-nodejs
  • 用户中心项目(登录 + 用户管理功能后端)
  • 嵌入式相机WEB,用C直接处理?
  • LeetCode_31_中等_下一个排列
  • huggingface的transformers训练gpt