【落羽的落羽 C语言篇】数据存储简介

server/2024/12/23 16:29:20/

在这里插入图片描述

文章目录

  • 一、整型提升
    • 1. 概念
    • 2. 规则
  • 二、大小端字节序
    • 1. 概念
    • 2. 练习
      • 练习1
      • 练习2
  • 三、浮点数在内存中的存储
    • 1. 规则
    • 2. 练习

一、整型提升

1. 概念

C语言中,整型算术运算至少是以“缺省整型类型”(int)的精度来进行的。为了达到这个精度,比int类型精度低的char和short等短整型类型的操作数,在使用前会转换成普通整型(int),这种转换被称为整型提升
不太好理解,我们先看一个奇怪的现象:

char a = 'a';
char b = 'b';
char c = a + b;
printf("a的ASCII值:%d\n", a);
printf("b的ASCII值:%d\n", b);
printf("c的值:%d\n", c);

你觉得这段代码的结果是什么样的?c的值是不是a和b的ASCII值相加呢?
在这里插入图片描述

很反直觉的结果。
这是因为a和b是char类型的数据,a+b在进行计算前,要进行整型提升,然后执行加法运算得到一个占据4字节的int类型数据,但这个数据要存储在只有一字节大小的c里呀。所以结果会被截断,然后再存储进c中。具体规律我们再来看:

2. 规则

进行整型提升的规则是:

  • 有符号整数的提升:补码高位补充符号位数
  • 无符号整数的提升:补码高位补充0

以char类型为例,char也属于整型家族,是有符号的类型

假如有char a = -1;,-1本来是一个十进制数,会占据四个字节,补码是11111111 11111111 11111111 11111110,但变量a只有八个比特位,只能存下前八位11111111。
如果之后的整数运算用到了变量a,就要先对a进行整型提升,11111111的符号位是1,高位补充1,结果是11111111 11111111 11111111 11111111(补码),即十进制数-1。

假如有char b = 1;,1本来是一个十进制数,会占据四个字节,补码是00000000 00000000 00000000 00000001,但变量b只有八个比特位,只能存下前八位00000001。
如果之后的整数运算用到了变量b,就要先对b进行整型提升,00000001的符号位是0,高位补充0,结果是00000000 00000000 00000000 00000001(补码),即十进制数1。

上面的两个例子中,a和b进行整型提升后,它们的值仍是-1和1。
但假如有char c = 128;,是什么结果呢?128本来是一个十进制数,会占据四个字节,补码是00000000 00000000 00000000 10000000,但变量c只有八个比特位,只能存下前八位10000000。这时,符号位变成了1。后续使用变量c进行整数运算时,要先对它整型提升,高位补充符号位1,结果是11111111 11111111 11111111 10000000(补码),是十进制数-128!

在这里插入图片描述

通过这个规律,我们可以总结出一个很重要的规则:由于整型提升的存在,char类型只有八个二进制位,char类型的变量只能存放-128 ~ 127的数,是一个循环。如果有char x = 127, y = x+1;那么y的值实际上就是-128。
在这里插入图片描述在这里插入图片描述

而若是unsigned char类型变量,由于是无符号类型,没有符号位,八个二进制位能表示出的最大十进制数是255,这种类型变量就只能存放0 ~ 255的数。
同样的道理,short类型的变量只能存放-32768 ~ 32767的数。

举个栗子:

char a = -128;
printf("%u",a);//%u是十进制的无符号整数

这段代码的结果是一个42亿多的数字。这是因为-128的补码本来是11111111 11111111 11111111 10000000,但a中只能存放前八位10000000。打印a也是一种对a的运算,要先进行整型提升。符号位是1,提升后是11111111 11111111 11111111 10000000。但%u说明这是一个无符号整数,最高位的1也要计算,最后这个32个二进制位换算成十进制就是42亿多的一个数。

在这里插入图片描述

在这里插入图片描述

二、大小端字节序

1. 概念

我们之前已经了解过整数在内存中的存储方式了——补码。
我们再来观察一个细节:
在这里插入图片描述

调试的时候,我们可以看到a中的0x11223344这个十六进制数是按着字节为单位,在内存中倒着存储的。(四个二进制数换算成一个十六进制数,也就是两个十六进制数占据一个字节)

超过一个字节的数据在内存中存储的时候,就会有存储顺序的问题,按照正序存放或是逆序存放,我们分为大端字节存储和小端字节存储模式:

  • 大端字节存储模式:数据的低位字节内容保存在内存的高地址处,数据的高位字节内容保存在内存的低地址处。如数0x1123344在内存中,四个地址由低到高存放的分别是11 22 33 44。

  • 小端字节存储模式:数据的低位字节内容保存在内存的低地址处,数据的高位字节内容保存在内存的高地址处。如数0x1123344在内存中,四个地址由低到高存放的分别是44 33 22 11。

回看上图,可以看出我的系统环境是小端字节存储模式。

2. 练习

练习1

百度的一道笔试题:设计一个小程序来判断当前机器的字节序

思路:我们将整数1存放到一个int变量中,看一看它四个字节中最地址最低的数据是什么。1的十六进制是0x00000001,四个字节由地址低到高:如果是大端应该是00 00 00 01,如果是小端就应该是01 00 00 00

#include<stdio.h>
int check()
{int i = 1;return *(char*)&i;
//char*强制类型转换&i,再解引用,就能找到i的四个当中地址最低的字节的内容,00或01
}int main()
{int ret = check();if(ret==1)//如果ret是1,说明i中的1存放形式是01 00 00 00,是小端printf("小端");else//如果ret是0,说明i中的1存放形式是00 00 00 01,是大端printf("大端");return 0;
}

练习2

这段代码的结果是什么

//X86环境,小端字节序模式
#include<stdio.h>
int main()
{int a[4] = {1,2,3,4};int* p1 = (int*)(&a+1);int* p2 = (int*)((int)a+1);printf("%x,%x",p1[-1],*p2);//%x是十六进制数,但VS打印会省略非0数前的所有0和xreturn 0;
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

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

1. 规则

浮点数类型包括float、double、long double等
浮点数在内存中的存储方式相对复杂:
根据国际标准IEEE 754,任意一个二进制浮点数V都可以表示成下面的形式:

V = (-1)S × M × 2E

  • (-1)S 表示符号位,当S=0时,V为整数。当S=-1时,V为负数。
  • M表示有效数字,M大于等于1,小于2。
  • 2E 表示指数位。

举例,十进制的5.0,写成二进制是101.0,相当于1.01×22。按照上面V的格式,S=0,M=1.01,E=2。十进制的-5.0,写成二进制是-101.0,相当于-1.01×22。按照上面V的格式,S=1,M=1.01,E=2。

同时,IEEE 754还规定:

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

前面说过,1<=M<2,也就是说,M的个位一定是1。因此,IEEE 754还规定,在计算机内部保存M时,默认这个数第一位总是1,因此可以被省略,只保存小数部分。比如保存1.012345时,只保存012345,等到读取的时候,再把第一位的1加上去。这样做的目的是节省一份空间,相对于可以多存储一位数字。

至于指数E,情况就比较复杂。
E是一个无符号整数(unsigned int)。如果E为8位,它的取值范围是0 ~ 255;如果E为11位,它的取值范围是0 ~ 2047。但是,科学计数法的E可以是负数。所以IEEE 754还规定,存入内存的真实值必须再加上一个中间数,对于8位的E中间数是127,对于11位的E中间数是1023。举个例子,如果一个浮点数的E是12,它保存成32位浮点数时,E会被保存成12+127=139,即10001011;它保存成64位浮点数时,E会被保存成12+1023=1035,即10000001011。

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

  • E中既有0也有1:将E的二进制序列换算成十进制数,减去127(或1023),得到真实值
  • E全为1:如果M不是0的话,V就是正负无穷大
  • E全为0:V是无限接近于0的很小的数字

理解就好~

2. 练习

通过前面的知识,思考一下这段代码的结果是什么:

#include<stdio.h>
int main()
{int n = 9;float* p = (float*)&n;*p = 9.0;printf("%d", n);return 0;
}

答案是:
在这里插入图片描述

解析:
将float类型的9.0存入n的32的比特位时,要遵循32位浮点数的存储规则。9.0的二进制是1001.0,即(-1)0 × 1.001 × 23,那么,第一位的符号位E是0,有效数字M等于001后面再加20个0补全23位,指数E等于3+127等于130,即10000010 。
所以,9.0在内存中的存储是:0 10000010 00100000000000000000000,但如果要被当成整数来解析时,这就是整数在内存中的补码了,直接换算成十进制的结果就是1091567616。

在这里插入图片描述

本篇完,感谢阅读


http://www.ppmy.cn/server/152535.html

相关文章

游戏AI实现-寻路算法(Dijkstra)

戴克斯特拉算法&#xff08;英语&#xff1a;Dijkstras algorithm&#xff09;&#xff0c;又称迪杰斯特拉算法、Dijkstra算法&#xff0c;是由荷兰计算机科学家艾兹赫尔戴克斯特拉在1956年发现的算法。 算法过程&#xff1a; 1.首先设置开始节点的成本值为0&#xff0c;并将…

JavaScript事件循环案例深入理解

事件循环的主要步骤&#xff1a; 执行栈&#xff08;Call Stack&#xff09;&#xff1a; 同步代码直接进入栈中依次执行。 任务队列&#xff08;Task Queue&#xff09;&#xff1a; 异步任务&#xff08;如 setTimeout、DOM 事件、Ajax 回调&#xff09;完成后将其回调函数放…

VSCode 插件开发实战(三):插件配置项自定义设置

前言 作为一名前端开发者&#xff0c;您可能已经在 VSCode 中体验过各种强大的插件。那么&#xff0c;如果您希望创建一个属于自己的插件&#xff0c;并且希望用户能够通过自定义配置进行灵活调整&#xff0c;该如何实现呢&#xff1f;本文将详细介绍如何在 VSCode 插件中实现…

jmeter怎么调用python

jmeter中使用python脚本 在jmeter中使用python脚本&#xff0c;搜了下&#xff0c;找到三种方式&#xff1a; 1、使用Jython包 下载 Download Jython 2.7.0 - Standalone Jar 包&#xff0c;放到jmeter/lib/目录下&#xff0c;重启jmeter&#xff0c;就能在sampler中找到JSR…

【SH】在Ubuntu Server 24中基于Python Web应用的Flask Web开发(实现POST请求)学习笔记

文章目录 Flask开发环境搭建保持Flask运行Debug调试 路由和视图可变路由 请求和响应获取请求信息Request属性响应状态码常见状态码CookieSession 表单GET请求POST请求 Flask 在用户使用浏览器访问网页的过程中&#xff0c;浏览器首先会发送一个请求到服务器&#xff0c;服务器…

第十四届蓝桥杯Scratch国赛真题—转动的车轮

转动的车轮 编程实现&#xff1a; 转动的车轮&#xff08;车轮使用画笔绘制&#xff0c;画面中不能出现其他角色&#xff0c;否则0分&#xff09;。 注&#xff1a;角色、背景非源素材。 具体要求&#xff1a; 1). 点击绿旗&#xff0c;背景如图所示&#xff1b; 2). 等待1…

【系统架构设计师】真题论文: 论软件设计方法及其应用(包括解题思路和素材)

更多内容请见: 备考系统架构设计师-专栏介绍和目录 文章目录 真题题目(2019年 试题1)解题思路论文素材参考软件设计方法概述和使用场景项目案例-结构化设计方法真题题目(2019年 试题1) 软件设计(Software Design,SD)根据软件需求规格说明书设计软件系统的整体结构、划…

电脑连接不上手机热点 找不到到服务器的ip地址

手机热点连接不上 找不到到服务器的ip地址 emmm希望不会有人不会吧 解决方法&#xff1a; 1.点击右上角图标进入设置 2.点击更改所有wifi网络的DNS设置 3.查看自己的IP分配和DNS分配是不是DHCP自动分配&#xff0c;不是的话就不对了&#xff0c;需要点击编辑手动改一下 4.改完…