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

 引言

数据是程序运行的核心。当我们用C语言编写程序时,我们实际上是在操纵内存中的数据。这些数据在内存中是如何储存的,今天我们就来学习这些内容。

基本数据类型

1.整型

int: 基本整型,通常占用4个字节

short: 短整型,通常占用2个字节

long: 长整型,通常占用4个字节

long long: 更长的整型,8个字节

signed: 有符号整型,可以表示正数、负数和零

unsigned: 无符号整型,只能表示非负整数

2.浮点型

float: 单精度浮点型,通常占用4个字节

double: 双精度浮点型,通常占用8个字节

long double: 扩展双精度浮点型,精度高于double,占用的字节数也更多,有 16 字节、12 字节、8 字节,其中 16 字节占大多数

3.字符型

char: 字符型,通常占用1个字节

4.复合数据类型

数组

结构体

联合体

枚举

其中,结构体、联合体和枚举我将会单独写一篇文章为大家介绍,敬请期待~

5.指针类型

//32位环境下指针变量大小 4
//64位环境下指针变量大小 8
char*:字符指针
short*:短整型指针
int*:整型指针
long*:长整型指针
long long*:更长类型指针
float*:单精度浮点数指针
double*:双精度浮点数指针
void*:空类型指针

6.void类型

void 类型通常用于表示没有返回值或没有参数的函数,或者用于声明一个指针,该指针不指向任何具体类型的数据

整数在内存中的储存

1.原码、反码、补码

在之前学习位操作符时,我们就有学习到:

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

原码、反码、补码是计算机中用于表示和处理有符号整数的三种编码方式

三种表示方法均由符号位和数值位组成,最高位为符号位

符号位用0表示正数,用1表示负数

正数的原码反码补码均相同

负数的原码反码补码均不同

原码:直接将数值按照正负数的形式转换为二进制得到的就是原码

反码:原码符号位不变,其他位按位取反得到的就是反码

补码:反码+1得到的就是补码

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

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

               2.补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路

也可以看看我之前写的文章,或许会更有利于理解

C语言——位操作符详解

2.signed和unsigned

2.1 signed

signed表示有符号的整数类型。有符号整数的表示方法是采用二进制补码,最高位(最左边的一位)是符号位,用于表示正负,0表示正,1表示负

C语言中的有符号整数类型包括signed char、signed short、signed int和signed long,它们的取值范围和精度取决于编译器和平台的实现。例如,signed char的取值范围是-128到127,占用1个字节(8位)的存储空间

2.2 unsigned

与signed相对,unsigned表示无符号的整数类型。无符号整数只能表示非负数(包括零),其范围从零到正的最大值

unsigned整数在计算机中的存储方式与有符号整数有所不同。对于无符号整数,所有的位都用于表示数值,没有专门的符号位

在C语言中,常见的无符号整数类型有unsigned char、unsigned short、unsigned int和unsigned long。它们的取值范围通常是从0到某个正的最大值,这个最大值取决于整数类型占用的位数

大小端

我们来看一段代码并试着通过调试查看一下内存

int main()
{
	int a = 0x11223344;

	return 0;
}

fc344a93a7de43c590bf06bed89b8430.png

我们可以看到,a中的0x11223344这个数字似乎是按照字节为单位,倒着排序的,这是为什么呢?

其实这是小端字节序系统的特性

接下来我们来学习一下大小端

1.什么是大小端

大小端是计算机系统中关于字节序的一个术语,它描述的是多字节数据在内存中的存放顺序。具体来说,大小端涉及到如何排列一个数值的多个字节。

大端:高位字节存放在内存的低地址端,低位字节存放在内存的高地址端。这种存储方式符合人类常规的数值读写习惯,即先读高位字节,再读低位字节。

小端:低位字节存放在内存的低地址端,高位字节存放在内存的高地址端。这种存储方式在计算机内部处理数据时较为常见,因为大多数计算机电路都是先处理低位字节,再处理高位字节,从而提高处理效率。

来看个图:

73cdde529c0d49db8e3a3e977f5d42ff.png

2.为什么有大小端

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

大小端各有其优缺点,适用于不同的场景。例如,小端模式在处理数值计算时更为高效,因为CPU在进行数值计算时,低位与低位相加,只需要顺序读取内存地址。而大端模式在判断数据的正负和大小方面更为方便,因为数据的符号位位于其对应的内存地址的第一个字节。

3.判断大小端

方法1:

最简单的方法自然是直接通过调试查看内存啦

a26d3c305b4f415fb0d478b0fe105a37.png

方法2:

通过代码判断

7620c722f8c4444fad41be74bbc45324.png

我们需要取出n的地址的第一位,怎么样才能取出第一位呢?

我们可以用 char* 取出n的地址的第一位

在小端字节序的计算机中,int类型的1在内存中的表示是0x01 00 00 00(假设int是4字节)。因此,通过char指针解引用得到的第一个字节的值是0x01,即十进制的1

而在大端字节序的计算机中,int类型的1在内存中的表示是0x00 00 00 01。通过char指针解引用得到的第一个字节的值是0x00,即十进制的0

代码实现如下:

int main()
{
	int n = 1;	//0x00 00 00 01
	//小端01 00 00 00
	//大端00 00 00 01
	if (*(char*)&n == 1) //char* 为一个字节
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

整型截断

1.什么是整型截断

整型截断是指在将一个字节大的整型数据赋值给一个字节小的整型变量时,发生的数据丢失现象

整型数据的存储大小是固定的,例如char类型通常占用1个字节,int类型通常占用4个字节。当我们试图将一个int类型的值赋给一个char类型的变量时,由于char类型的存储空间较小,无法容纳int类型的全部数据,因此会发生截断

2.例子

来看个示例

int main()
{
	int a = -1;
	char b = a;
	printf("%d", b);
	return 0;
}

a的原码:10000000000000000000000000000001

a的反码:11111111111111111111111111111110

a的补码:11111111111111111111111111111111

由于b是char类型的,只有1个字节,占8个比特位,当a赋值给b时,会出现整型截断,最低8位保留,最高24位舍弃

b的补码:11111111

b的反码:11111110

b的原码:10000001

因此b等于-1

整型提升

1.什么是整型提升

整型提升是一种隐式类型转换机制。C语言中字节数少于整型字节数的数据类型在进行整型运算时,该类型的数据会被默认转为整型数据

其中,该类型的数据被转化为整型数据的过程就称为整型提升

2.例子

那数据是如何进行整型提升的?

有如下两条规则:

如果是无符号数,则高位直接补0
如果是有符号数,则高位全补符号位

例子1:

char a=1;

补码:00000001

有符号数,符号位为0,整型提升后为:00000000 00000000 00000000 00000001

例子2:

char b=-1;

补码: 11111111

有符号数,符号位为1,整型提升后为:11111111 11111111 11111111 11111111

例子3:

unsigned c=-1;

补码:11111111

无符号数,高位全部补0,整型提升后为:00000000 00000000 00000000 11111111

练习题

1.练习题1

int main() 
{
    char a = -1;
    //   a的原码:10000000000000000000000000000001
    //   a的反码:11111111111111111111111111111110
    //   a的补码:11111111111111111111111111111111
    signed char b = -1;
    unsigned char c = -1;
    //a、b、c均存储为11111111
    printf("a=%d b=%d c=%d", a, b, c);
    //发生整型提升:
    //a、b有符号,整型提升为:11111111111111111111111111111111
    //c无符号,整型提升为:00000000000000000000000011111111
    return 0;
}

输出结果为:

a=-1 b=-1 c=255

2.练习题2

int main() 
{
    char a = -128;
    //原码:10000000000000000000000010000000
    //反码:11111111111111111111111101111111
    //补码:11111111111111111111111110000000
    //发生整型截断:10000000
    printf("%u", a);
    //发生整型提升:11111111111111111111111110000000
    //%u打印无符号整型:4294967168
    return 0;
}

输出结果为:

4294967168

3.练习题3

int main() 
{
    char a[1000];
    int i = 0;
    for (int i = 0; i < 1000; i++)
    {
        a[i] = -1 - i;
        //有符号char最小值为:11111111 ->-127
        //C语言特别规定10000000->-128
        //由于最高位为符号位
        //所以最大值为:01111111->127
    }
    printf("%d", strlen(a));    //strlen以'\0'(ASCII为0)为结束标志
    //当i=128时
    //-129的补码:11111111111111111111111101111111
    //发生整型截断
    //01111111->127
    //因此a[i]储存的数据为-1、-2、-3......-128、127、126......1、0
    //长度=127+128=255
    return 0;
}

输出结果为:

255

为了更方便我们理解,我们可以尝试用画图来直观的看一下

补充:图中的数字均以补码形式表示

循环会从-1开始,逆时针旋转,直到遇到0为止

所以a[i]储存的数据为-1、-2、-3......-128、127、126......1、0

长度=127+128=255

4.练习题4

int main()
{
    unsigned char i = 0;
    for (i = 0; i <= 255; i++)
    {
        printf("hello world\n");
    }
    //由于i是无符号字符类型
    //最小值为0,最大值为255
    //i在递增到255时再+1又会回到0
    //因此代码会循环打印hello world
    return 0;
}

输出结果:

循环打印 hello world

5.练习题5

int main()
{
	unsigned int i;
	//i为无符号整数
	for (i = 9; i >= 0; i--)
	{
		printf("%u ", i);
	}
	//先打印9 8 7 6 5 4 3 2 1 0
	//然后到-1
	//由于i是unsigned int类型,不能表示负数
	//当i递减到0并执行i--操作时,它不会变成-1
	//而是变成unsigned int能够表示的最大值
	//即2^32-1
	//因此循环条件i>=0始终为真
	//最终导致死循环
	return 0;
}

输出结果为:

9 8 7 6 5 4 3 2 1 0......(死循环)

浮点数在内存中的存储

1.浮点数的存储规则

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

V = (−1) ^S*M *2^E
• (-1)^S 表示符号位,当S=0,V为正数;当S=1,V为负数
• M表示有效数字,M是大于等于1,小于2的
• 表示指数位

2.例子

为了方便我们理解,我们来看个例子:

十进制的5.0,写成二进制是101.0,相当于1.01×2^2

那么,根据V的格式,我们可以得知:

S=0,M=1.01,E=2

IEEE 754规定:

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

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

3.浮点数的存与取

3.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是一个无符号整数(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

3.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的很小的数字

E全为1

255 - 127 = 128 或 2047 - 1023 = 1024, 与第二点相反,这时这个数无穷大

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

4.例题解析

int main()
{
	int n = 9;
	float* pfloat = (float*)&n;
	printf("n的值为:%d\n", n);
	printf("*pfloat的值为:%f\n", *pfloat);

	*pfloat = 9.0;
	printf("n的值为:%d\n", n);
	printf("*pfloat的值为:%f\n", *pfloat);
	return 0;
}

输出结果为:

n的值为:9
*pfloat的值为:0.000000
n的值为:1091567616
*pfloat的值为:9.000000

我们来分析一下:

1.将9的二进制序列按照浮点数的形式拆分

  9的补码为:0000 0000 0000 0000 0000 0000 0000 1001

  写成浮点数表示的形式:0 00000000 00000000000000000001001

  我们可以得到:符号位S=0,指数位E=00000000,有效数字位M=00000000000000000001001

  由于指数E全为0,符合E全为0的情况,因此浮点数V写成:

  V=(-1)^0× 0.00000000000000000001001×2^(-126)=1.001×2^(-146)

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

2.n被改为浮点数9.0,以整数的方式打印,遵循整型的存储方式 

  浮点数9.0等于二进制的1001.0

  用V表示:V=1.001×2^3

  所以9.0=(-1)^0 ×1.001×2^3

  我们可以得到:符号位S=0,有效数字M等于001后面补充20个0,凑满23位,指数E=3+127=130

  即10000010

  因此浮点数V写成二进制形式为:

  0 10000010 00100000000000000000000

  这个32位的二进制数,被当作整数进行解析时,就是整数在内存中的补码

  以整数形式打印就是1091567616

3.浮点数以浮点数形式打印,输出结果为9.000000

结束语

磨蹭了好久,终于是把数据在内存中的存储的内容写完了。

希望看到这里的友友们能点赞收藏关注

十分感谢!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python学习从0到1 day25 第二阶段 SQL ② Python操作数据库

少年有梦&#xff0c;不应至于心动&#xff0c;更要付诸行动 —— 24.4.12 pymysql 除了使用图形化工具以外&#xff0c;我们也可以使用编程语言来执行SQL从而操作数据库 在Python中&#xff0c;使用第三方库&#xff1a;pymysql来完成对MySQl数据库的操作 安装 pip install py…

[Kubernetes[K8S]集群:Slaver从节点初始化和Join]:添加到主节点集群内

文章目录 操作流程&#xff1a;上篇主节初始化地址&#xff1a;前置&#xff1a;Docker和K8S安装版本匹配查看0.1&#xff1a;安装指定docker版本 **[1 — 8] ** [ 这些步骤主从节点前置操作一样的 ]一&#xff1a;主节点操作 查看主机域名->编辑域名->域名配置二&#x…

一台电脑上安装多个软件不同版本

工作中经常需要用到不同版本的jdk、nodejs等 以nodejs为例&#xff0c;使用哪个版本将哪个版本挪到上方&#xff1a;

SpringCloud、SpringBoot、JDK版本对应关系

SpringCloud与SpringBoot 版本 官网说明&#xff1a;https://spring.io/projects/spring-cloud#overview SpringBoot 与 JDK版本关系 发布说明&#xff1a;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Release-Notes SpringBoot 3.x不再支持JDK1.…

基于STC12C5A60S2系列1T 8051单片机的带字库液晶显示器LCD12864数据传输并行模式显示汉字应用

基于STC12C5A60S2系列1T 8051单片机的液晶显示器LCD12864显示汉字应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍液晶显示器LCD12864简单介绍一、LCD12864点阵型液…

Python分支结构

我们刚开始写的Python代码都是一条一条语句顺序执行&#xff0c;这种代码结构通常称之为顺序结构。 然而仅有顺序结构并不能解决所有的问题&#xff0c;比如我们设计一个游戏&#xff0c;游戏第一关的通关条件是玩家在一分钟内跑完全程&#xff0c;那么在完成本局游戏后&#x…

【日常记录】【JS】一道解构面试题

文章目录 1、描述2、分析与实现3、参考链接 1、描述 让这一段代码可以执行&#xff0c;并且正确输出 let [name, age] {name: 呆呆狗,age: 20}console.log(name, age);2、分析与实现 在浏览器上执行这段代码会报错 翻译以下&#xff1a;不是可迭代对象 可迭代对象&#xff08;…

【计算机毕业设计】基于Java+SSM的实战开发项目150套(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f9e1;今天给大家分享150的Java毕业设计&#xff0c;基于ssm框架&#xff0c;这些项目都经过精心挑选&#xff0c;涵盖了不同的实战主题和用例&#xff0c;可做毕业设计和课程…

965: 循环队列

解法&#xff1a;顺序表实现 #include<iostream> #include<vector> using namespace std; struct SeqList {int* data;int front;int rear;int len; }; void initList(SeqList* list,int size) {list->data new int[size];list->len size;list->front …

sudo apt install ros-humble-gazebo-*显示网络不可达 Ubuntu20.04使用清华镜像本地安装/更新ros2

问题 sudo apt install ros-humble-gazebo-*显示网络不可达&#xff0c;这是因为sources.list中的镜像源有问题&#xff0c;换成清华源可以解决问题 解决 1 设置Ubuntu镜像源为清华镜像源 1.1 备份source.list文件 sudo cp /etc/apt/sources.list /etc/apt/sources.list.ba…

实况窗助力美团打造鸿蒙原生外卖新体验,用户可实时掌握外卖进展

自2023年华为宣布全新HarmonyOS NEXT蓄势待发&#xff0c;鸿蒙原生应用全面启动以来&#xff0c;已有金融、旅行、社交等多个领域的企业和开发者陆续宣布加入鸿蒙生态。其中&#xff0c;美团作为国内头部的科技零售企业&#xff0c;是首批加入鸿蒙生态的伙伴&#xff0c;其下的…

【vue】toRef,toRefs

toRef&#xff1a;把一个 响应式对象 转换为对应的ref变量toRefs&#xff1a;把一个 响应式对象 转换为对应的ref对象 代码 <template><P>mname: {{ mname }} </P><P>mage: {{ mage }} </P><P>msex: {{ msex }} </P><P>mhobb…

fastjson 序列化问题

问题: 使用fastjson 的 对同一个JSONObject对象 多次引用后, 通过 JSON.toJSONString() 方法进行json序列化时出现只有第一次的可以成功序列化未json string 字符串, 后面的对象都为引用地址; 示例: public static void main(String[] args) {JSONObject jsonObject new JSON…

2024最新信息系统项目管理师--(1抵100)真题考过的十大管理点(细致)

备注&#xff1a; 1.这里将汇总所有管理的输入、输出、使用的工具 2.对真题考过的点&#xff0c;将会标红处理&#xff0c;并进行细致说明 3. 针对最新的改版点&#xff0c;用绿色标记 信息系统项目管理师第4版&#xff08;5大过程、10大管理&#xff09; 十大管理启动过程组…

CVPR‘24| Leap-of-Thought! 中大/哈佛等提出CLoT探究大模型幽默创新响应

Leap-of-Thought! 中大/哈佛等提出CLoT探究大模型幽默创新响应 什么是“大喜利”创新响应游戏&#xff1f;为什么考虑“大喜利”游戏&#xff1f;性能结果展示激发创造力的思维方式Leap-of-Thought (LoT)通向LoT! 激发创造力的训练方法CLoT性能评估总结 (导读) 多模态大模型具备…

【Java系列】SpringCloudAlibaba统一返回体及全局异常捕获实现

本文将以实际代码展示如何实现SpringCloudAlibaba的统一返回体及全局异常捕获。 作者&#xff1a;后端小肥肠 1. 前言 在构建微服务应用时&#xff0c;统一返回体和异常捕获机制的设计对于保持代码的整洁性和提高服务的可维护性至关重要。特别是在使用 Spring Boot 和 Spring …

数据结构初阶:二叉树(一)

树概念及结构 树的概念 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的 。 有一个特殊的结点&a…

1688商品详情接口技术深探:解锁电商数据新纪元,实现业务自动化飞跃

1688商品详情接口技术解析 一、引言 随着电子商务的快速发展&#xff0c;越来越多的企业开始关注如何利用API接口获取商品详情信息&#xff0c;以实现数据的自动化处理和业务的快速拓展。1688作为国内知名的B2B电商平台&#xff0c;其商品详情接口成为了众多企业关注的焦点。…

HarmonyOS鸿蒙端云一体化开发--适合小白体制

端云一体化 什么是“端”&#xff0c;什么是“云”&#xff1f; 答&#xff1a;“端“&#xff1a;手机APP端 “云”:后端服务端 什么是端云一体化&#xff1f; 端云一体化开发支持开发者在 DevEco Studio 内使用一种语言同时完成 HarmonyOS 应用的端侧与云侧开发。 …

AI预测体彩排3第3弹【2024年4月14日预测--第1套算法开始计算第3次测试】

今天咱们继续测试第1套算法和模型&#xff0c;今天是第3次测试&#xff0c;目前的测试只是为了记录和验证&#xff0c;不建议大家盲目跟买。我的目标仍旧是10次命中3-4次!~废话不多说了&#xff0c;直接上结果&#xff01; 2024年4月14日排3的七码预测结果如下 第一套&…