C语言-数据在内存中的存储

我们再之前的篇目中有提到整数在内存中的存储,那么本篇文章将会为大家带来更为详细的内容,包括大小端字节序,以及浮点数如何在内存中存储。

目录

1.整数在内存中的存储

2.大小端字节序和字节序判断

2.1什么是大小端?

2.2为什么有大小端?

2.3数据在内存中存储习题

2.3.1练习1

2.3.2练习2

2.3.3练习3

2.3.4练习4

2.3.5练习5 

2.3.6练习6

2.3.7练习7

2.3.8练习8

3.浮点数在内存中的存储

3.1练习

3.2浮点数的存储

3.2.1浮点数存的过程

3.2.2浮点数取的过程

3.2.2.1E不全为0或不全为1

3.2.2.2E全为0

3.2.2.3E全为1

3.3题目解析


1.整数在内存中的存储

  • 简单回顾:

二进制数字的原码、反码、补码

整数的二进制表示方法有三种:原码、反码、补码。

首先对于有符号整数来说,三种表示方法都分为符号位+数值位,符号位是二进制序列中最高的地位,如果符号位是0则表示该数是正数,如果符号位是1则表示该数是负数。

原码:将最高位作为符号位(0表示正,1表示负),其它数字位代表数值本身的绝对值的数字表示方式。

反码:如果是正数,则表示方法和原码一样;如果是负数,符号位不变,其余各位取反,则得到这个数字的反码表示形式。

补码如果是正数,则表示方法和原码一样;如果是负数,则将数字的反码加上1(相当于将原码数值位取反然后在最低位加1)

Rule 1:正整数的原码、反码和补码都相同。

Rule 2:负整数的原码、反码和补码不相同,直接将数值按照正负数的形式翻译成二进制得到的就是原码,将原码的符号位不变,其他位依次按位取反就可以得到反码,反码+1就得到补码。

Rule 3:补码得到原码也是可以使用:取反,+1的操作。

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

为什么呢?

在计算机系统中,数值一律用补码来表示和存查。原因在于,使用补码可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

2.大小端字节序和字节序判断

我们先来看下面代码,并对他进行调试,可以发现一些细节

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

整数n的值为11223344,在内存中占4个字节,即11 22 33 44。而我们如果在VS上对代码进行调试,可以发现n的存储顺序是倒着存储的

2.1什么是大小端?

当超过一个字节的数据在内存中存储的时候,就会储顺序的问题,按照不同的存储顺序,可以分为大端字节序存储和小端字节序存储,以下是具体概念:

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

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

如此我们便能分辨大小端的存储模式。

2.2为什么有大小端?

那为什么会有大小端模式之分呢?

原因:

在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit 位。但是C语言中除了 8bit 的char类型之外,还有 16bit 位的short类型,32bit 的long类型(具体看编译器)。此外,对于位数大于8位的位处理器,例如16或者32的位处理器,由与寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题,因此就导入了大端存储模式和小端存储模式。

例如:

一个16bit位的short型x,在内存中的地址为0x0010,x的值为0x11220 , 那么0x11为高字节,0x22位低字节。对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

2.3数据在内存中存储习题

2.3.1练习1

请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)-百度笔试题

#include <stdio.h>int check_sys()
{int n = 1;return *(char*)&n;
}int main()
{int ret = check_sys();if (ret == 1){printf("小端\n");}else{printf("大端\n");}return 0;
}

注意:

强制转换类型(char)n 是没有办法做到的,强制类型转换不论存储方式都会取出该整数最后一个字节的内容

2.3.2练习2

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

signed char图解:

unsigned char图解:

注意:

VS上 char = signed char

由此可以推出short(-32768~32767)、unsigned short(0~65535)

输出结果:

a=-1,b=-1,c=255

2.3.3练习3

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

a的原码:1000 0000 0000 0000 0000 0000 1000 0000

a的反码:1111 1111 1111 1111 1111 1111 0111 1111

a的补码:1111 1111 1111 1111 1111 1111 1000 0000

然而char只有一个字节大小,即a在内存中的存储:1000 0000

%u是以无符号整数的形式进行打印,则a需要进行整型提升:

a提升后的补码:1111 1111 1111 1111 1111 1111 1000 0000

对于无符号整数(恒大于0),补码即是原码,转换为十进制就是输出的结果。

输出结果:

4294967168

2.3.4练习4

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

a的原码:0000 0000 0000 0000 0000 0000 1000 0000

对于整数,原码就是补码,然而char只有一个字节大小,即a在内存中的存储:1000 0000

%u是以无符号整数的形式进行打印,则a需要进行整型提升:

a提升后的补码:1111 1111 1111 1111 1111 1111 1000 0000

对于无符号整数(恒大于0),补码即是原码,转换为十进制就是输出的结果。

输出结果:

4294967168

2.3.5练习5 

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

依据char的图解,一个周期是256,'\0'的ASCII码值为0,从-1到0一共走了255,即数组元素的第256元素a[255]='\0',strlen统计的是'\0'前字符的个数,所以结果为255。

2.3.6练习6

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

结果:

死循环,unsigned char(0~255)

2.3.7练习7

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

结果:

死循环,unsigned int最小值为0,-1得到最大值,以此往复。

2.3.8练习8

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

ptr2:

a + 1:+1跳过一个int类型

(int*)((int)a + 1):+1跳过一个字节

注意:

%x是以十六进制数的形式进行打印,%#x可以将前缀0x打印出来,高位不够自动补0。

3.浮点数在内存中的存储

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

3.1练习

以下代码的结果是什么?

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

输出结果:

n的值为:9
*pFloat的值为:0.000000
num的值为:1091567616
*pFloat的值为:9.000000

现象:

定义n为整数9,那么以浮点数的视角解取出的数据不是9;对于浮点数9.0,以整数的视角取出的数也不是9.0。

结论:

整数的存储方式和浮点数的存储方式不同。

3.2浮点数的存储

上面的代码中,num和&pfloat在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?

其原因是因为整数的存储方式和浮点数的存储方式不同。

那么我们要想搞清楚这个结果,一定要搞懂浮点数在计算机内部的表示方法。

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

V=(-1)^{S}*M*2^{E}

1.(-1)^{S}表示符号位,当S=0,V为整数;当S=1,V为负数。

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

3.2^{E}表示指数位

举例:

十进制浮点数5.0,写成二进制是101.1,相当于1.011 x 2^2。

那么,按照上面V的格式,可以得出S=0,M=1.011,E=2。

IEEE 754 规定:

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

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

3.2.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位有效数字。

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

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

这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须要再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。

举例:

5.5的二进制形式为101.1,S=0,M=1.011,E-2。其E为2+127=129,在内存中存储为 1000 0001,而M去掉1为011,补齐到23位为01100000000000000000000,则其二进制表示形式为:

0 10000001 01100000000000000000000

十六进制形式为:40 B0 00 00

调试:

#include <stdio.h>int main()
{float f = 5.5f;return 0;
}

我们可以在VS上对上面的代码进行调试,看看是否如我们所看到的那样

可以发现,我们以小端的模式将浮点数5.5存储进去了,符合我们上面的分析。

注意:

大家在自己举例的时候,不要举特殊的数字,如3.14这样的数字,小数点后面化成二进制十分困难,并且有许多位,如果小数点后的位太多,就有可能导致浮点数在内存中无法精确的保存。

举例:

#include <stdio.h>int main()
{float f = 3.14159265f;printf("%0.8f\n", f);return 0;
}

输出结果:

3.14159274

可以发现这个数就存在了一定的偏差。

3.2.2浮点数取的过程

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

3.2.2.1E不全为0或不全为1

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

0 10000001 01100000000000000000000
3.2.2.2E全为0

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

0 00000000 00100000000000000000000
3.2.2.3E全为1

这时,浮点数的指数E可能为128(或2024)即为真实值,此时浮点数可以表示为V = (-1)^S * 1.xxx * 2^128,此时表示的是±无穷大(正负取决于符号位S);

0 11111111 00010000000000000000000

3.3题目解析

我们现在回过去再看3.1的练习,就能很清楚明白为什么9还原成浮点数,就成了0.000000,以及为什么9.0还原成整数,会是一个很大的数字。

9以二进制补码的形式存储在内存中:

00000000 00000000 00000000 00001001

首先,将9的二进制序列按照浮点数的形式拆分,得到S=0,E=-127,即

(-1)^0 * 0.00000000000000000001001 * 2^-126

此时是E为全0的情况,表示±0,以及接近0的很小的数字

再看第二环节,浮点数9.0,为什么整数打印是1091567616

首先,浮点数90为二进制1001.0,即换算成科学计数法是:1.001 * 2^3

此时S=0,M=1.001,E=130即10000010

那么它在内存中存储应该是S+E+M,即

0 10000010 00100000000000000000000

如果以整数的视角来看,这是一个很大的数,而结果就是1091567616。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/314380.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第三篇:Python编程基础:掌握核心语法与开发技巧

Python编程基础&#xff1a;掌握核心语法与开发技巧 1 引言 在这个信息化迅速蔓延的世界中&#xff0c;Python语言如同钥匙一般开启了通往各种可能性的大门。无论你是数据科学家、网络工程师、机器学习专家&#xff0c;还是仅仅对自动化办公感兴趣的办公室人员&#xff0c;Pyt…

Linux 调度优先级

Linux中的每个任务都有其优先级。这个优先级的范围从-20到19。优先级越低&#xff08;-20&#xff09;&#xff0c;分配 给任务的CPU时间就越多。默认的优先级是0。 并非所有的任务都需要使用相同的优先级。交互式应用要求快速响应&#xff0c;通过 crontab 运行的后台…

Feign负载均衡

Feign负载均衡 概念总结 工程构建Feign通过接口的方法调用Rest服务&#xff08;之前是Ribbon——RestTemplate&#xff09; 概念 官网解释: http://projects.spring.io/spring-cloud/spring-cloud.html#spring-cloud-feign Feign是一个声明式WebService客户端。使用Feign能让…

实验7 利用三层交换机实现VLAN间路由

实验7 利用三层交换机实现VLAN间路由 一、 原理描述二、 实验目的三、 实验内容1.实验场景2.实验要求 四、 实验配置1.实验拓扑2.设备编址 五、 实验步骤1.配置IP地址2.交换机初始配置3.测试连通性4.配置S1的三层接口 一、 原理描述 在“单臂路由”方式实现VLAN间路由时&#…

Kafka 生产者应用解析

目录 1、生产者消息发送流程 1.1、发送原理 2、异步发送 API 2.1、普通异步发送 2.2、带回调函数的异步发送 3、同步发送 API 4、生产者分区 4.1、分区的优势 4.2、生产者发送消息的分区策略 示例1&#xff1a;将数据发往指定 partition 示例2&#xff1a;有 key 的…

playwright 使用

pip install playwright 是一个命令&#xff0c;用于通过 Python 的包管理工具 pip 安装 Playwright 库。Playwright 是一个用于端到端网页测试的库&#xff0c;支持多种浏览器&#xff0c;包括 Chromium、Firefox 和 WebKit。 执行 pip install playwright 命令后&#xff0c…

Android使用ProtoBuf 适配 gradle7.5 gradle8.0

ProtoBuf 适配 Gradle7.5 gradle-wrapper.properties 配置 distributionUrlhttps\://services.gradle.org/distributions/gradle-7.5-bin.zipProject&#xff1a;build.gradle: plugins {id com.android.application version 7.4.2 apply falseid com.android.library versio…

python-opencv实现最近邻插值和双线性插值对图片上采样

使用背景 当我们需要把图像进行放大或者缩小的时候&#xff0c;第一反应是使用resize()实现。很多情况下&#xff0c;我们会调用最近邻插值和双线性插值去放大图片&#xff0c;当然要说没有分辨率的损失那是不可能的&#xff0c;只能说在放大图片的过程中尽可能增加了图片的分…

React复习笔记

基础语法 创建项目 借助脚手架&#xff0c;新建一个React项目(可以使用vite或者cra&#xff0c;这里使用cra) npx create-react-app 项目名 create-react-app是React脚手架的名称 启动项目 npm start 或者 yarn start src是源文件index.js相当于Vue的main.js文件。整个…

C语言:一维数组、二维数组、字符数组介绍

数组 介绍一维数组定义应用方法初始化 举例示例结果 二维数组定义应用方法初始化 举例示例结果 字符数组定义应用方法初始化 举例示例结果分析 介绍 在C语言中&#xff0c;数组是一种基本的数据结构&#xff0c;用于存储一系列相同类型的数据。数组可以是多维的&#xff0c;最…

【嵌入式】Arduino IDE + ESP32开发环境配置

一 背景说明 最近想捣鼓一下ESP32的集成芯片&#xff0c;比较了一下&#xff0c;选择Arduino IDE并添加ESP32支持库的方式来开发&#xff0c;下面记录一下安装过程以及安装过程中遇到的坑。 二 下载准备 【1】Arduino IDE ESP32支持一键安装包&#xff08;非常推荐&#xff0…

miniTry:Python实现web搜索(全自动+程序操控)

声明&#xff1a;本问给出了全部代码--可以复现--亲测有效 :) [ 代码为图片--> 强制自己去敲一次 又不多] 1.打开网站&#xff1a; 2.利用id去定位到我们要进行输入的内容&#xff08;bing可以直接进行搜索&#xff0c;而csdn需要登录&#xff0c;所以我们用csdn做演示&…

python 使用flask_httpauth和pyjwt实现登录权限控制

最近需要用到&#xff0c;学习了一下记录 首先安装依赖 pip install Flask-HTTPAuth pyjwt passlib Welcome to Flask-HTTPAuth’s documentation! — Flask-HTTPAuth documentation Welcome to PyJWT — PyJWT 2.8.0 documentation Passlib 1.7.4 documentation — Passl…

Java8 Stream常见用法

Stream流的常见用法&#xff1a; 1.利用stream流特性把数组转list集合 //定义一个数组Integer[] array {5,2,1,6,4,3};//通过stream特性把数组转list集合List<Integer> list Arrays.stream(array).collect(Collectors.toList());//打印结果System.out.println(list);…

Docker深入探索:网络与资源控制、数据管理与容器互联以及镜像生成

目录 一、 Docker网络 &#xff08;一&#xff09;Docker网络实现原理 &#xff08;二&#xff09;Docker网络模式 1. Bridge网络&#xff08;默认&#xff09; 2. Host网络 3. None网络 4. Container网络 5. 自定义网络 二、资源控制 &#xff08;一&#xff09;cgr…

从递归角度串联二叉树-图论-动态规划

一、深度理解二叉树的前中后序遍历 二叉树遍历框架如下&#xff1a; void traverse(TreeNode* root) {if (root nullptr) {return;}// 前序位置traverse(root->left);// 中序位置traverse(root->right);// 后序位置 }先不管所谓前中后序&#xff0c;单看 traverse 函数…

分布式与一致性协议之CAP(五)

CAP 理论 如何使用BASE理论 以InfluxDB系统中DATA节点的集群实现为例。DATA节点的核心功能是读和写&#xff0c;所以基本可用是指读和写的基本可用。我们可以通过分片和多副本实现读和写的基本可用。也就是说&#xff0c;将同一业务的数据先分片&#xff0c;再以多份副本的形…

Rust中的函数指针

什么是函数指针 通过函数指针允许我们使用函数作为另一个函数的参数。函数的类型是 fn &#xff08;使用小写的 ”f” &#xff09;以免与 Fn 闭包 trait 相混淆。fn 被称为 函数指针&#xff08;function pointer&#xff09;。指定参数为函数指针的语法类似于闭包。 函数指…

如何在TestNG中忽略测试用例

在这篇文章中&#xff0c;我们将讨论如何在TestNG中忽略测试用例。TestNG帮助我们忽略使用Test注释的情况&#xff0c;我们可以在不同的级别上忽略这些情况。 首先&#xff0c;只忽略一个测试方法或测试用例。第二&#xff0c;忽略一个类及其子类中的所有情况。第三个是&#…

【深度学习】YOLOv5,烟雾和火焰,目标检测,防火检测,森林火焰检测

文章目录 数据收集和数据标注查看标注好的数据的脚本下载yolov5创建 dataset.yaml训练参数开始训练yolov5n训练训练后的权重下载gradio部署 数据收集和数据标注 搜集数据集2w张。 pip install labelme labelme 然后标注矩形框和类别。 下载数据请看这里&#xff1a; https:…