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

数据在内存中的存储(了解大小端字节序浮点数在内存中存储)详细~

目录

1、整数在内存中的存储

2、了解大小端字节序

2.0 为什么有大小端之分呢?

3、练习题

3.1 练习01

3.2 练习02

3.3 练习03

3.4 练习04

3.5 练习05

3.6 练习06

4、浮点数在内存中的存储

4.0 浮点数在计算机内部的表示方法

4.1 浮点数存的过程

4.2 浮点数取的过程

4.3 回到开始时示例的代码,分析:


1、整数在内存中的存储

整数的2进制表示方法有三种,即原码、反码和补码

有符号的整数,三种表示方法均有符号位数值位两部分,符号位都是用0表示“正”,用1表示"负",最高位的一位是被当做符号位,剩余的都是数值位。

正整数的原、反、补码都相同。

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

原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。

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

补码:反码+1就得到补码。

对于整型来说:数据存放内存中其实存放的是补码。

原因:使用补码,可以将符号位和数值域统一处理;

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

2、了解大小端字节序

在计算机科学中,字节序或端序是指多字节数据在内存中的存储顺序。主要有两种字节序:大端字节序(Big-Endian)小端字节序(Little-Endian)

大端(存储)模式: 是指数据的低位字节(LSB)内容保存在内存的高地址处,而数据的高位字节(MSB)内容,保存在内存的低地址处。

小端(存储)模式: 是指数据的低位字节(LSB)内容保存在内存的低地址处,而数据的高位字节(MSB)内容,保存在内存的高地址处。

✅示例: 

#include <stdio.h>
int main()
{int a = 0x11223344;return 0;
}

调试的时候,我们可以看到在a中的 0x11223344 这个数字是按照字节为单位,倒着存储的。

2.0 为什么有大小端之分呢?

产生原因:

在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节(8位)。然而,在C语言等编程语言中,除了8位的char类型外,还有16位的short型、32位的long型等数据类型。对于位数大于8位的处理器(如16位、32位或64位处理器),由于寄存器的宽度大于一个字节,因此必须解决如何将多个字节安排到内存中的问题。这就导致了大端模式和小端模式的产生。

应用场景:

我们常用的 X86 结构是小端模式,而KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

1、硬件架构:不同的硬件架构可能采用不同的字节序方式。例如,Intel x86系列处理器采用的是小端模式,而MIPS、PowerPC等处理器则采用的是大端模式
2、文件格式:在文件格式中,常常需要使用特定的字节序来表示数据。例如,BMP图像文件中,像素数据通常采用小端模式存储;而WAV音频文件中,样本数据则采用大端模式存储。
3、网络传输:在网络传输数据时,通常需要将数据转换成网络字节序(即大端模式),以确保在不同机器之间的传输中不会出现问题。因此,大多数协议规定了网络字节序应该采用大端模式
4、数据库存储:在数据库中,常常需要对多字节数据类型进行排序和比较。由于不同的字节序方式会影响排序结果,因此在数据库设计中需要考虑字节序问题。

3、练习题

3.1 练习01

设计一个小程序来判断当前机器的字节序。

#include<stdio.h>// 设计一个小程序来判断当前机器的字节序int main()
{int num = 1;if (*(char*)&num == 1) // int*{printf("小端\n");}else{printf("大端\n");}return 0;
}

在VS2022 X86下运行结果:

函数写法:

#include<stdio.h>// 函数写法
//int check_sys() // 第一种
//{
//	int num = 1;
//	if (*(char*)&num == 1)
//		return 1; // 小端
//	else
//		return 0; // 大端
//}
// 
int check_sys() // 第二种
{int num = 1;return *(char*)&num; // 返回1是小端返回0是大端
}int main()
{if (check_sys() == 1){printf("小端\n");}else{printf("大端\n");}}

3.2 练习02

代码:

#include <stdio.h>
int main()
{char a = -1;signed char b = -1;unsigned char c = -1;printf("a=%d,b=%d,c=%d\n", a, b, c); // a=-1,b=-1,c=255 return 0;
}

解析:

%d —— 打印有符号的整数,%u 打印的是无符号的整数。其中a是有符号的a, b也是有符号的,打印结果一样,而unsigned char 则是无符号的,所以我们先找出-1的补码,而c存储在char时存8位(即11111111),但要以%d的形式打印,需要整型提升,c是unsigned char,无符号位的,高位数补0,结果为(00000000 00000000 00000000 11111111),再以%d打印,打印的是有符号的数 ,符号位又为0,为正数,原反补相同。所以内存存的为这个(00000000 00000000 00000000 11111111)补码的话,原码也一样为(00000000 00000000 00000000 11111111),所以结果为正的255。

运行结果:

char 是有符号还是无符号的这个不太确定,取决于编译器~但是大部分编译器上char==signed char

3.3 练习03

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

解析:

%u —— 打印的是无符号的整数。在%u 的角度,它认为在内存中存储的是无符号的整数。

-128 原码为(10000000000000000000000010000000),求出补码(11111111111111111111111110000000 ),char a 只能存储8位(即10000000)要打印%u的无符号整数,所以要整型提升,char为有符号char ,高位补1,补符号位。结果为(11111111111111111111111110000000)提升完,当作是内存内的补码,%u 打印的是无符号的整数,故原反补相同,所以原码也是(11111111111111111111111110000000),结果用计算机算出为:(4294967168)

改编一下代码:

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

结果一样的:思路跟上面差不多,就不多解析了。

实际上char 类型的取值范围(signed char)为-128~127 ,当你给128 时根本存不下,相当于内存放的就是-128。所以结果一样。如果是无符号char( unsigned char )取值范围是0~255。第一位就不是符号位了。

扩展:( signed short)类似的。

格式决定了我如何看待数据,有时候跟它的类型没太大关系。

如下面的代码,结果是一样的,不建议这样写代码,最好是signed int 就用%d 打印,是unsigned int 就用%u 打印

3.4 练习04

代码:

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

strlen 求得是字符串的长度,统计的是 \0 之前出现的字符个数,我们要找到数组中 0 的位置,如图所示,已知char 的范围为-128~127。

3.5 练习05

代码:

循环条件恒成立(恒为真),代码死循环。

#include <stdio.h>
unsigned char i = 0; // 全局变量
// 0~255
int main()
{for (i = 0; i <= 255; i++){printf("hello world\n"); // 死循环打印hello world}return 0;
}

unsigned char ,无符号整型最小值为0,循环条件不可能小于0,恒成立,死循环。 

#include <stdio.h>
#include<windows.h>
int main()
{unsigned int i;for (i = 9; i >= 0; i--){printf("%u\n", i);// 死循环Sleep(100);// 休眠查看}return 0;
}

3.6 练习06

代码:

#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);// 4,  2000000// // %x 是十六进制打印return 0;
}

解析:

&a 则是取出了整个数组的地址,&a+1跳过了整个数组,赋给ptr1 强制类型转换,ptr1 指向同个位置,ptr1[-1]---> *(ptr1-1),整型指针解应用得到 4

a 为数组名,就是首元素的地址,强转为int ,把它当作整数,(int)a + 1为整数+1,其实只是向后偏移一个字节而已。*(ptr2),整型指针解引用,访问四个字节 00 00 00 02,在内存中它是小端存放,要还原成真实值为 0x02 00 00 00,前面的0x0不打印,所以结果为2000000

 

4、浮点数在内存中的存储

常见的浮点数:3.14159、1E10等,浮点数家族包括: float、double、long double 类型。 浮点数表示的范围: float.h 中定义

✅示例:

#include <stdio.h>
int main()
{int n = 6;float* pFloat = (float*)&n;printf("n的值为:%d\n", n);printf("*pFloat的值为:%f\n", *pFloat);*pFloat = 6.0;printf("num的值为:%d\n", n);printf("*pFloat的值为:%f\n", *pFloat);return 0;
}

运行结果:

观察得到:如果我以整数的形式放进去,以浮点数取出,结果不对,反之,我以浮点数的形式放进去,以整数取出结果也不对。但是以整数(浮点数)的形式放进去,以整数(浮点数)取出,结果对。下面的例子说明了整数和浮点数在内存中存储方式有区别。

4.0 浮点数在计算机内部的表示方法

根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:

V   =  (−1)^s ∗ M ∗ 2^E

(−1)^s 表示符号位,当S=0,V为正数;当S=1,V为负数

M 表示有效数字,M是大于等于1,小于2的

2^E 表示指数位

10进制转为2进制简单示例:

二进制中,其实就是把底数的10变成了2,如在10进制中,1.011x10^2=101.1,而在2进制中,把底数10变成了2,1.011x2^2=101.1

IEEE 754规定:

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

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

4.1 浮点数存的过程

IEEE 754 对有效数字M和指数E的一些特别规定。

二进制数,1≤M<2,而M可以写成 1.xxxxxx 的形式,其中xxxxxx是小数部分

IEEE 754 规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的 xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位(即1加上尾数位的23位)有效数字。

至于指数E,情况就比较复杂

首先,E为一个无符号整数(unsigned int)

● 如果E为8位,它的取值范围为0~255;

● 如果E为11位,它的取值范围为0~2047。

但是,我 们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数。

● 对于8位的E,这个中间数是127;

● 对于11位的E,这个中间数是1023。

如:2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

     保存为64位浮点数的时候,E保存为10+1023=1033,即10000001001。

4.2 浮点数取的过程

指数E从内存中取出还可以再分成三种情况

① E不全为0或不全为1

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

比如:0.5 的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则1.0*2^(-1),其阶码(指数位)为-1+127(中间值)=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位 00000000000000000000000,则其二进制表示形式为:

0 01111110 00000000000000000000000

这里S为0,E为01111110,M为00000000000000000000000

又比如:

 下面这两种情况比较少见:

② E全为0

这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0以及接近于0的很小的数字

③ E全为1

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

4.3 回到开始时示例的代码,分析:

为什么 6 还原成浮点数,就成了 0.000000 ? 6以整型的形式存储在内存中,得到如下二进制序列:

0 00000000 00000000000000000000110

首先,将 6 的二进制序列按照浮点数的形式拆分,得到第一位符号位S = 0,后面8位的指数 E=00000000 , 最后23位的有效数字M = 00000000000000000000110。由于指数E全为0,符合E为全0的情况。因此,浮点数V就写成:

V=0.00000000000000000000110 * 2^(-126)= 1.10*2^(-147)

显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000。

浮点数6.0,为什么整数打印是1086324736呢?

浮点数6.0的二进制0110.1 换算成科学技术法:1.100*2^2

故:6.0=(-1)^0 * 1.100 * 2^2

所以第一位的符号位S=0,有效数字M等于100后⾯再加20个0,凑满23位,指数E等于2+127=129, 即010000001,写成二进制形式,S+E+M:

 0 10000001 100 0000 0000 0000 0000 0000

这个32位的二进制数,被当做整数来解析的时候,就是整数在内存中的补码,原码也一样,所以最终结果为 1086324736 。

喜欢的话

⛳ 点赞☀收藏 ⭐ 关注!

如有不足欢迎评论区指出~

Respect!!!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • zabbix实战-磁盘空间告警
  • 华为鸿蒙Core Vision Kit 骨骼检测技术
  • 构建第一个Spring项目
  • 黑神话悟空什么配置可以玩?什么样的游戏本配置可以畅玩《黑神话:悟空》?黑神话悟空电脑配置推荐
  • WEB之文件上传
  • 华为M60首次降价,消费回暖能延续?
  • 【FreeRTOS】队列实验-多设备玩游戏(旋转编码器)
  • SQL 时间盲注 (injection 第十五关)
  • java.sql.SQLException: txn too large, size: 104857606.
  • SQLALchemy 分组过滤、子查询
  • QT网络编程: 实现UDP通讯设置
  • java使用itext 直接生成pdf
  • 2025ICASSP Author Guidelines
  • 宠物空气净化器推荐买吗?清除浮毛的效果好吗
  • 改变自己·心情治愈
  • “大数据应用场景”之隔壁老王(连载四)
  • Apache的80端口被占用以及访问时报错403
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • IE报vuex requires a Promise polyfill in this browser问题解决
  • JavaScript服务器推送技术之 WebSocket
  • Linux快速复制或删除大量小文件
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • spring-boot List转Page
  • ucore操作系统实验笔记 - 重新理解中断
  • 闭包--闭包之tab栏切换(四)
  • 关于字符编码你应该知道的事情
  • 计算机在识别图像时“看到”了什么?
  • 理清楚Vue的结构
  • 入口文件开始,分析Vue源码实现
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 译米田引理
  • 大数据全解:定义、价值及挑战
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​iOS安全加固方法及实现
  • (13)DroneCAN 适配器节点(一)
  • (JS基础)String 类型
  • (pojstep1.1.1)poj 1298(直叙式模拟)
  • (分享)自己整理的一些简单awk实用语句
  • (附源码)ssm户外用品商城 毕业设计 112346
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (转)http协议
  • (转)jdk与jre的区别
  • (自用)网络编程
  • (最全解法)输入一个整数,输出该数二进制表示中1的个数。
  • ****Linux下Mysql的安装和配置
  • .bat批处理(九):替换带有等号=的字符串的子串
  • .mysql secret在哪_MYSQL基本操作(上)
  • .net core 依赖注入的基本用发
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .net redis定时_一场由fork引发的超时,让我们重新探讨了Redis的抖动问题
  • .NET 材料检测系统崩溃分析
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况
  • .netcore如何运行环境安装到Linux服务器
  • .NET开发人员必知的八个网站