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

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

在这里插入图片描述

文章目录

  • 一、整型提升
    • 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和x
    return 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.kler.cn/a/452750.html

相关文章:

  • Leecode刷题C语言之字符串及其反转中是否存在同一子字符串
  • 【开源免费】基于SpringBoot+Vue.JS安康旅游网站(JAVA毕业设计)
  • Python爬虫(入门+进阶)
  • 【LuaFramework】服务器模块相关知识
  • 【求职面试】驾照的种类
  • OAuth 2.0
  • 车载网关性能 --- 缓存buffer划分要求
  • 109.【C语言】数据结构之求二叉树的高度
  • 探究人工智能在教育领域的应用——以大语言模型为例
  • 【JAVA高级篇教学】第五篇:OpenFeign 微服务调用注意事项
  • docker commit生成的镜像瘦身
  • 参数名在不同的SpringBoot版本中,处理方案不同
  • 深度学习笔记1:神经网络与模型训练过程
  • Java设计模式 —— 【结构型模式】享元模式(Flyweight Pattern) 详解
  • C++-----------数组
  • Linux复习2——管理文件系统1
  • 数据可视化期末复习-简答题
  • golang,多个proxy拉包的处理逻辑
  • MT6765核心板_MTK6765安卓核心板规格参数_联发科MTK模块开发
  • 结构化Prompt:让大模型更智能的秘诀
  • 保姆级教程Docker部署RabbitMQ镜像
  • 【Linux】如何对比两个文件数据不同的地方
  • python+reportlab创建PDF文件
  • Vulnhub之Cengbox 2靶机详细测试过程(利用不同的方法提权)
  • 数据结构之栈,队列,树
  • 从想法到实践:Excel 转 PPT 应用的诞生之旅