C语言:数据在内存中的存储
目录
- 一、 整数在内存中的存储
- 二、 大小端字节序和字节序判断
- 1.什么是大小端
- 2.为什么有大小端
- 3.练习
- (1)练习1
- (2)练习2
- (3)练习3
- (4)练习4
- (5)练习5
- (6)练习6
- 三、 浮点数在内存中的存储
- 1.练习
- 2.浮点数的存储
- (1) 浮点数存的过程
- (2)浮点数取的过程
- 3.题目解析
一、 整数在内存中的存储
在讲解操作符的时候,我们就讲过了下⾯的内容:
整数的2进制表示方法有三种,即 原码、反码和补码
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位最高位的⼀位是被当做符号位,剩余的都是数值位。
正整数的原、反、补码都相同。
负整数的三种表示方法各不相同。
原码: 直接将数值按照正负数的形式翻译成二进制得到的就是原码。
反码: 将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码: 反码+1就得到补码。
对于整形来说:数据存放内存中其实存放的是补码。
为什么呢?
在计算机系统中,数值⼀律用补码来表示和存储。
原因在于,使用补码,可以将符号位和数值域统一处理;
同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
二、 大小端字节序和字节序判断
当我们了解了整数在内存中存储后,我们调试看一个细节:
#include <stdio.h>
int main()
{int a = 0x11223344;return 0;
}
调试的时候,我们可以看到在a中的 0x11223344
这个数字是按照字节为单位,倒着存储的。这是为什么呢?
嘿嘿,这就要说到大小端的问题了,且听我仔细到来。
1.什么是大小端
其实超过一个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为⼤端字节序存储和小端字节序存储,下面是具体的概念:
大端(存储)模式: 是指数据的低位字节内容保存在内存的高地址处,而数据的高位字节内容,保存在内存的低地址处。
小端(存储)模式: 是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的高地址处。
上述概念需要记住,方便分辨大小端。
2.为什么有大小端
为什么会有大小端模式之分呢?
这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit 位,但是在C语言中除了8 bit 的 char
之外,还有16 bit 的 short
型,32 bit 的 long
型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度⼤于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如:⼀个 16bit
的 short
型 x
,在内存中的地址为 0x0010
, x
的值为 0x1122
,那么0x11
为高字节, 0x22
为低字节。对于大端模式,就将 0x11
放在低地址中,即 0x0010
中,0x22 放在高地址中,即 0x0011
中。小端模式,刚好相反。我们常用的 X86
结构是小端模式,而KEIL C51
则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
3.练习
(1)练习1
请简述大端字节序和小端字节序的概念,设计⼀个小程序来判断当前机器的字节序。(10分)-百度笔试题
首先来看一张解析图:
从图中我们可以发现,只要判断n第一个字节地址处的内容是1还是0,就能得知是大端存储的还是小端存储的。
那么代码就可以这样实现:
//代码1
#include <stdio.h>
int check_sys()
{int n= 1;return (*(char *)&n);//取出n的地址强制转换为char*类型,再解引用访问的就是//内存中n第一个字节的内容
}
int main()
{int ret = check_sys();if(ret == 1){printf("⼩端\n");}else{printf("⼤端\n");}return 0;
}
像这道题还有没有其他的实现方法呢?当然了,这种方法更加巧妙的应用到了一个知识点:联合体(union)
这里简单列举一下联合体的特点:
- 成员首地址相同,也就是通过不同的成员访问会从相同的起始地址开始取数据,至于取多少个地址的数据取决于具体成员的类型
- union内存空间的分配按照其占用内存最大的成员来进行分配
那么代码怎么实现呢?
//代码2
int check_sys()
{union{int i;char c;}un;un.i = 1;return un.c;
}
因为它们共用一块内存空间,而且不同的成员访问会从相同的起始地址开始取数据,又因为un.c是字符类型数据,所以返回un.c也就是返回un.i第一个字节处的内容,怎么样,有没有被惊讶到呢?😎联合体不是怎么懂?没关系,下一期,我们就会详细讲到联合体是干什么用的。
(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;
}
这个的打印结果会是什么呢?仔细想一想
a=-1,b=-1,c=255
(3)练习3
#include <stdio.h>
int main()
{char a = -128;printf("%u\n", a);return 0;
}
那我们来分析一下:
int main()
{char a = -128;//128为十进制数//-128的源码:10000000 00000000 00000000 10000000//-128的反码:11111111 11111111 11111111 01111111//-128的补码:11111111 11111111 11111111 10000000//a是char类型只能存8个比特位//所以存的是10000000printf("%u\n", a);//a是char类型,首先要整型提升//a是char类型,最高位是符号位,所以直接补符号位//提升后:11111111 11111111 11111111 10000000//%u的形式打印,是认为a中存放的是无符号数//所以直接以十进制的形式打印这个数return 0;
}
打印结果为:
4294967168
那再来一道题:
#include <stdio.h>
int main()
{char a = 128;printf("%u\n",a);return 0;
}
相信此时的你心里已经有答案了趴!直接照猫画虎按照上面的思路来做就可以了,那答案会是什么呢?会和上面一样吗?
(4)练习4
#include <stdio.h>
int main()
{char a[1000];int i;for(i=0; i<1000; {a[i] = -1-i;}printf("%d",strlen(a));//求的是字符串的长度,统计//的是\0(ACSII码值是0)之前的字符个数return 0;
}
这道题a的长度会是多少呢?
255
为什么不是1000呢?来看一张图:
而stelen求的是\0(ACSII码是0)之前的字符个数,也就是char能存储的最小值到最大值的个数,128+127=255,所以当把0赋值给arr[i]的时候,strlen就已经计算出结果了。
(5)练习5
//第一道
#include <stdio.h>
unsigned char i = 0;
int main()
{for(i = 0;i<=255;i++){printf("hello world\n");}return 0;
}//第二道
#include <stdio.h>
int main()
{unsigned int i;for(i = 9; i >= 0; i--){printf("%u\n",i);}return 0;
}
代码输出的结果是啥?死循环?相信聪明的你一定会发现其中的端倪滴😋
(6)练习6
#include <stdio.h>
//x86 小端字节序下
int main()0
"
{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;
}
相信第一个打印的数字肯定难不倒你,那第二个呢?
咱们来分析一下第二个打印的结果会是什么?看图:
打印结果为:
4,2000000//如果想打印成这种形式:0x4,0x02000000只需要以%#x打印就可以了
三、 浮点数在内存中的存储
常见的浮点数:3.14159、1E10等,浮点数家族包括:float
、double
、long double
类型。
浮点数表示的范围:float.h
中定义
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;
}
输出什么?
2.浮点数的存储
上面的代码中,num
和 *pFloat
在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?
要理解这个结果,一定要搞懂浮点数在计算机内部的表示方法。
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
V = (−1)的S次方* M *2的E次方
- (−1)的S次方表示符号位,当S=0,V为正数;当S=1,V为负数
- M 表示有效数字,M是大于等于1,小于2的
- 2的E次方表示指数位
举例来说:
十进制的5.0,写成二进制是 101.0
,相当于 1.01×2^2
。
那么,按照上面V的格式,可以得出S=0,M=1.01,E=2。
十进制的-5.0,写成二进制是 -101.0
,相当于 -1.01×2^2
。那么,S=1,M=1.01,E=2。
IEEE 754规定:
对于32位的浮点数,最高的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M
对于64位的浮点数,最高的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M
(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。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
(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
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
0 00000000 00100000000000000000000
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);
0 11111111 00010000000000000000000
好了,关于浮点数的表示规则,就说到这里。
3.题目解析
下面,让我们回到一开始的练习
先看第1环节,为什么 9
还原成浮点数,就成了 0.000000
?
9以整型的形式存储在内存中,得到如下二进制序列:
0000 0000 0000 0000 0000 0000 0000 1001
首先,将 9
的二进制序列按照浮点数的形式拆分,得到第一位符号位s=0,后面8位的指数E=00000000
,最后23位的有效数字M=000 0000 0000 0000 0000 1001。
由于指数E全为0,所以符合E为全0的情况。因此,浮点数V就写成:
V=(-1)^0 × 0.00000000000000000001001×2^(-126)=1.001×2 ^(-146)
显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000。
再看第2环节,浮点数9.0,为什么整数打印是 1091567616
首先,浮点数9.0 等于二进制的1001.0,即换算成科学计数法是:1.001×2^3
所以: 9.0 = (−1)^0 ∗ (1.001) ∗ 2 ^3 ,
那么,第一位的符号位S=0,有效数字M等于001后⾯再加20个0,凑满23位,指数E等于3+127=130,即10000010
所以,写成二进制形式,应该是S+E+M,即
0 10000010 001 0000 0000 0000 0000 0000
这个32位的二进制数,被当做整数来解析的时候,就是整数在内存中的补码,原码正是1091567616 。
好了,数据在内存中的存储就是这样子的,相信聪明的你们看完这篇文章一定理解了趴,最后别忘了给博主一键三连哟!你们的支持就是博主坚持下去最大的动力!✨
相关文章:

C语言:数据在内存中的存储
目录 一、 整数在内存中的存储二、 大小端字节序和字节序判断1.什么是大小端2.为什么有大小端3.练习(1)练习1(2)练习2(3)练习3(4)练习4(5)练习5(6)练习6 三、 浮点数在内存中的存储1.练习2.浮点数的存储(1) 浮点数存的过程(2)浮点数取的过程 3.题目解析 一、 整数在内存中的存储…...

DP动态规划入门(数字三角形、破损的楼梯、安全序列)
一、动态规划(DP)简介 动态规划(Dynamic Programming,简称DP)是运筹学的一个分支,它是一种通过将复杂问题分解成多个重叠的子问题,并通过子问题的解来构建整个问题的解的算法。在动态规划中&am…...
HBase Shell的应用案例
电商( eshop)平台具有海量数据、高并发访问、高速读写等特征,适合使用HBase分布式数据库进行数据存储。本节通过一个 HBase在电商平台的应用案例,熟练掌握并综合运用HBase Shell命令行终端提供的各种操作命令。 一、电商(eshop)平台的逻辑数据模型 在H…...
Allegro许可管理技巧
在数字化时代,软件许可管理对于企业的运营至关重要。然而,许多企业在实施软件管理过程中会遇到各种问题。Allegro许可管理作为一款高效、合规的管理工具,能够帮助企业解决常见的许可管理问题。本文将深入探讨Allegro许可管理中的实用技巧&…...

34 vue 项目默认暴露出去的 public 文件夹 和 CopyWebpackPlugin
前言 这里说一下 vue.config.js 中的一些 public 文件夹是怎么暴露出去的? 我们常见的 CopyWebpackPlugin 是怎么工作的 ? 这个 也是需要 一点一点积累的, 因为 各种插件 有很多, 不过 我们仅仅需要 明白常见的这些事干什么的即可 当然 以下内容会涉及到一部分vue-cli,…...

Redis 不再“开源”,对中国的影响及应对方案
Redis 不再“开源”,使用双许可证 3 月 20 号,Redis 的 CEO Rowan Trollope 在官网上宣布了《Redis 采用双源许可证》的消息。他表示,今后 Redis 的所有新版本都将使用开源代码可用的许可证,不再使用 BSD 协议,而是采用…...
在CentOS中怎么安装和配置NginxWeb服务器
在CentOS中安装和配置Nginx Web服务器可以通过以下步骤完成: 1. 使用yum安装Nginx: sudo yum install nginx 2. 启动Nginx服务: sudo systemctl start nginx 3. 设置Nginx开机自启动: sudo systemctl enable nginx 4. 配置防火墙规…...
使用docker搭建Fluentd的教程
使用Docker搭建Fluentd的教程 步骤 1: 拉取Fluentd镜像 首先,需要从Docker Hub上拉取Fluentd的官方镜像: docker pull fluent/fluentd:v1.14-debian-1这里使用的是基于Debian的Fluentd 1.14版本的镜像,可以根据需要选择其他版本。 步骤 2…...
Python的re模块进行正则表达式操作时的常用方法[回顾学习]
re 模块是 Python 中用于处理正则表达式的标准库模块。通过 re 模块,可进行字符串匹配、搜索和替换等各种操作。 有几个常用的方法:# re.match(pattern, string):从字符串开头开始匹配模式,并返回匹配对象。适合用于确定字符串是否…...

Rust之构建命令行程序(五):环境变量
开发环境 Windows 11Rust 1.77.0 VS Code 1.87.2 项目工程 这次创建了新的工程minigrep. 使用环境变量 我们将通过添加一个额外的功能来改进minigrep:一个不区分大小写的搜索选项,用户可以通过环境变量打开该选项。我们可以将此功能设置为命令行选项,…...

ARMday7
VID_20240322_203313 1.思维导图 2.main.c #include"key_inc.h" //封装延时函数 void delay(int ms) {int i,j;for(i0;i<ms;i){for(j0;j<2000;j){}} } int main() {//按键中断的初始化key1_it_config();key2_it_config();key3_it_config();while(1){printf(&q…...
Ubuntu中安装VSCode的一个指令
问题描述 本来想去VSCode官网上下载软件包,然后双击使用Ubuntu Software安装的,但是安装老不成功。 想用命令行指令dpkg进行安装,虽然能成功,但是后续使用 code . 命令打开VSCode又报错说找不到命令。 解决方式 在命令行中使用…...
生活电子产品拆解分析~汇总目录
一、锂电池电源 ①电子产品拆解分析-暖手宝 ②电子产品拆解分析-电动牙刷 ③电子产品拆解分析-充电宝台灯 ④电子产品拆解分析-太阳能自动感应灯 ⑤电子产品拆解分析-人体感应灯 ⑥电子产品拆解分析-食物电子秤 ⑦电子产品拆解分析-6600mA充电宝 ⑨电子产品拆解分析-触摸化妆镜…...

Tkinter 一文读懂
Tkinter 简介 Tkinter(即 tk interface,简称“Tk”)本质上是对 Tcl/Tk 软件包的 Python 接口封装,它是 Python 官方推荐的 GUI 工具包,属于 Python 自带的标准库模块,当您安装好 Python 后,就可…...

2核4G服务器阿里云性能测评和优惠价格表
阿里云2核4G服务器租用优惠价格,轻量2核4G服务器165元一年、u1服务器2核4G5M带宽199元一年、云服务器e实例30元3个月,活动链接 aliyunfuwuqi.com/go/aliyun 活动链接如下图: 阿里云2核4G服务器优惠价格 轻量应用服务器2核2G4M带宽、60GB高效…...

Day41:WEB攻防-ASP应用HTTP.SYS短文件文件解析Access注入数据库泄漏
目录 ASP-默认安装-MDB数据库泄漏下载 ASP-中间件-CVE&短文件&解析&写权限 HTTP.SYS(CVE-2015-1635)主要用作蓝屏破坏,跟权限不挂钩 IIS短文件(iis全版本都可能有这个问题) IIS文件解析 IIS写权限 ASP-SQL注入-SQLMAP使用…...

什么是单点登录?
单点登录(Single Sign On,简称 SSO)简单来说就是用户只需在一处登录,不用在其他多系统环境下重复登录。用户的一次登录就能得到其他所有系统的信任。 为什么需要单点登录 单点登录在大型网站应用频繁,比如阿里旗下有淘…...

elasticsearch的数据搜索
DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括: 查询所有:查询出所有数据,一般测试用。例如:match_all 全文检索(full text)查询:利用分词器对用户…...
云原生相关概念(小白版)
先说概念: 云原生应该是一种“建立在云上的多种效率提升技术的复合体"(而不是单一的技术创新),主要就是在云技术摆脱物理储存限制的基础上,进一步实现应用的专业优化(即文章里说的按功能切分…...

Dell戴尔XPS 12 9250二合一笔记本电脑原装出厂Windows10系统包下载
链接:https://pan.baidu.com/s/1rqUEM_q5DznF0om6eevcwg?pwdvij0 提取码:vij0 戴尔原厂WIN10系统自带所有驱动、出厂主题壁纸、系统属性专属联机支持标志、系统属性专属LOGO标志、Office办公软件、MyDell等预装程序 文件格式:esd/wim/sw…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!
一、引言 在数据驱动的背景下,知识图谱凭借其高效的信息组织能力,正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合,探讨知识图谱开发的实现细节,帮助读者掌握该技术栈在实际项目中的落地方法。 …...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...