C 进阶 — 数据在内存中的存储

C 进阶 — 数据在内存中的存储

主要内容

1、数据类型详细介绍

2、整形在内存中的存储:原码、反码、补码

3、大小端字节序介绍及判断

4、浮点型在内存中的存储解析

一 数据类型介绍

基本内置类型

char        //字符数据类型  1
short       //短整型       2
int         //整形         4
long        //长整型		4
long long   //更长的整形    8
float       //单精度浮点数  4
double      //双精度浮点数  8// 规定    
sizeof(char)  ==  1
sizeof(char)  <=  sizeof(short)
sizeof(short) <=  sizeof(int)
sizeof(int)   <=  sizeof(long)
sizeof(long)  <=  sizeof(long long)

C 语言没有字符串类型,字符串类型是用字符数组实现的

类型:决定了开辟内存空间的大小和如何描述该块内存空间

1.1 类型的基本归类

整形

charunsigned charsigned charshortunsigned shortsigned short intunsigned intsigned intlongunsigned long signed long 

浮点型

float double

构造类型

> 数组类型
> 结构体类型 struct
> 枚举类型 enum
> 联合类型 union

指针类型

int *pi;
char *pc;
float* pf;
void* pv;

空类型

void 表示空类型(无类型)
通常应用于函数的返回类型、函数参数、指针类型

二 整型在内存中的存储

2.1 原码、反码、补码

计算机中的整数有三种二进制表示方法,即原码、反码和补码

三种表示方法均有 符号位 和 数值位 两部分,符号位都是用 0 表示 正 ,用 1 表示 负。数值位正数的原、反、补码都相同,而负整数的三种表示方法各不相同

  1. 原码 将数值按照正负数的形式翻译成二进制就可以得到原码
  2. 反码 原码的符号位不变,其他位依次按位取反就可以得到反码
  3. 补码 反码 +1 得到补码
2.2 原反补的意义

对于整形而言:数据存放内存中的是补码

因为可以将符号位和数值域统一处理,同时加减法也统一处理( CPU 只有加法器 )此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路

以下为原因探讨,参考

1、原码(Sign-Magnitude Representation)

原码是最简单的表示法,它用一位二进制数表示符号,0 表示正数,1 表示负数,其余位数表示数字的绝对值。例如,+5和 -5 的二进制原码分别是:

+5 的原码:00000101
-5 的原码:10000101

问题:原码虽然直观,但在进行二进制运算时,正负数的处理很麻烦,尤其是减法时,还需要额外判断符号位,增加了硬件电路的复杂性

2、反码(Ones’ Complement)

反码通过将负数的每一位进行取反( 1 变 0 ,0 变 1 )来表示负数。例如,+5 和 -5 的反码表示如下:

+5的反码:00000101(与原码相同)
-5的反码:11111010

优点:反码改进了原码的加减法处理,因为减法可以转化为加法,只需要加上一个负数的反码即可

问题:反码有一个严重的问题,即零的表示不唯一,存在 +0(00000000)和 -0(11111111)两种表示形式,这导致逻辑处理变得复杂

3、补码(Two’s Complement)

补码是最常用的表示法,它通过在反码的基础上再加 1 来表示负数。比如,+5 和 -5 的补码如下:

+5 的补码:00000101
-5 的补码:11111011(反码是 11111010,再加 1)

优点:① 唯一的零表示:补码只用一个零(00000000),解决了反码的双零问题 ② 加减法统一:补码的设计让加法和减法能够统一处理,所有数值都可以通过加法来计算,简化了硬件设计 ③ 符号处理自动化:补码的符号位(最高位)在计算过程中自动处理,无需额外逻辑

2.3 大小端

示例数据在内存中的存储

#include<stdio.h>/* 
短除法20/2 = 10 010/2 = 5  05/2  = 2  12/2  = 1  01/2  = 0  1    10100
*/
int main()
{    int a = 20; // 00000000 00000000 0000000 00010100// 14 00 00 00 十六进制 —> 二进制 (将每个十六进制数转换为 4 个二进制数字)// 00010100 00000000 00000000int b = -10; //10000000 00000000 00001010//取反 = 11111111  11111111 11110101//+1  = 11111111  11111111 11110110//转十六进制 = ff ff ff f6return 0;
}

可以看到对于 a 和 b 分别存储的是补码。但是发现顺序不一致

image-20241205161039490

image-20241205160916898

大小端

大端(存储)模式,是指数据低位保存在内存高地址中,而数据高位,保存在内存低地址

小端(存储)模式,是指数据低位保存在内存低地址中,而数据高位,保存在内存高地址

例如一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反

为什么有大小端之分 因为在计算机中,数据是以字节为单位的,一个字节为 8 bit。对于 32 位处理器,由于寄存器宽度大于一个字节,必然存在如何将多个字节安排的问题

2.4 练习

1、设计一个小程序判断当前机器的字节序

#include<stdio.h>
//方式一, 利用取地址 + 指针解引用
int check_sys()
{int i = 1;return *(char*) & i;
}// 方式二, 利用联合体特性
int check_sys()
{union {int i;char c;} un;un.i = 1;return un.c;
}int main()
{if (check_sys())printf("大端");elseprintf("小端");return 0;
}

2、下面程序分别都输出什么

/*① char 类型变量中存储的值的本质就是数字(存入字符时,字符会转化为 ASCLL 表中对应的数字再存入),因此char 类型的变量是可以直接存储数字的,只不过由于字节数的限制,存储范围较小② -1 的补码是 11111111111111111111111111111111,存入 char、signed char、unsigned char 类型变量中时,会发生截断,只将补码的八个低位端的 1 放入,即 11111111③ 打印时,由于打印格式是 %d,那么会发生整形提升。提升规则如下:有符号位则左端补充符号位,无符号位则左端补充 0。于是 a 补充为 11111111111111111111111111111111(char 一般默认为有符号型),b 补充为11111111111111111111111111111111,c 补充为 00000000000000000000000011111111。转化成原码输出,它们的结果分别是-1、-1、255
*/
#include <stdio.h>
int main()
{char a = -1;//-1 的补码 11111111111111111111111111111111 存入 char 发生截断, 即 11111111signed char b = -1;unsigned char c = -1;printf("a = %d, b = %d, c = %d",a,b,c); //打印时 %d 整形提升, 有符号位则左端补充符号位, 无符号位则左端补充0, 11111111111111111111111111111111 -> 转原码 -1return 0;
}
/*-128 = 10000000 00000000 00000000 10000000 原码= 11111111 11111111 11111111 01111111 反码= 11111111 11111111 11111111 10000000 补码存入 char 发生截断a =  10000000 (二进制)%u 按无符号整型输出, 发生提升 10000000 -> 00000000 00000000 00000000 10000000 补码-> 11111111 11111111 11111111 10000000 原码(补码取反 + 1, 这里要注意 %u 没有符号位, 所以原码不需要考虑符号位)
*/#include <stdio.h>
int main()
{char a = -128; //128 = 2^7 = 1000 0000printf("%u\n",a); //4294967168return 0;
}
/*128 = 00000000 00000000 00000000 10000000 原(补)码存入 char 发生截断a =  10000000 (二进制)%u 按无符号整型输出, 发生提升 10000000 -> 00000000 00000000 00000000 10000000 补码-> 11111111 11111111 11111111 10000000 原码(补码取反 + 1)
*/#include <stdio.h>
int main()
{char a = 128;printf("%u\n",a); //4294967168return 0;
}
/*-20 = 10000000 00000000 00000000 00010100 (原码)= 11111111 11111111 11111111 11101011 (反码)= 11111111 11111111 11111111 11101100 (补码)10  = 00000000 00000000 00000000 00001010 (原补码)+   = 11111111 11111111 11111111 11110110 (补码)->  = 10000000 00000000 00000000 00001010 (原码)这里要注意 %d 有符号位, 所以 11111111 11111111 11111111 11110110 也要被当成有符号数, 符号位为 1
*/
int i = -20;
// i + j, unsigned + signed, 就要转为 unsigned
unsigned  int  j = 10;
printf("%d\n", i + j); //-10 //最后一步需要注意 %d
//按照补码的形式进行运算,最后格式化成为有符号整数
/*9 = 00000000 00000000 00000000 00001001当 i == 0 时, i-- 则 i 变为 11111111 11111111 11111111 11111111 = 2^32 - 1死循环输出
*/
unsigned int i;
for(i = 9; i >= 0; i--)
{printf("%u\n",i);
}
/*数值 -1 - i = -1, -2, -3, -4-> 对应的二进制10000000 00000000 00000000 00010100char 1 个字节 8 bit-1 - i = 10000000 00000000 00000001 00000000 时存入 a[i] 中因发生截断, 存入数值 0 2^8 = 256, 此时 i = 256strlen 不包含 '\0', 因此 strlen 结果为 255阿拉伯数字 0 的ASCII码为 48,‘\0’ 转义字符的 ASCII码值为 0,它表示的是 ASCII 控制字符中空字符
*/int main()
{char a[1000];int i; //整型for(i=0; i<1000; i++){a[i] = -1-i;}printf("%d",strlen(a)); //255return 0;
}
/*i 是 unsigned char, 对应范围 [0, 255]当 i == 255 时, i++ 则 i = 0死循环打印
*/#include <stdio.h>
unsigned char i = 0;
int main()
{for(i = 0;i<=255;i++){printf("hello world\n");}return 0;
}

三 浮点数在内存中的存储

参考链接 浮点型在内存中的存储 ,浮点型在内存中的存储

浮点数在计算机内部的表示方式,根据国际标准IEEE754,任意一个二进制浮点数可表示成下面的形式:

(-1)^ S * M * 2 ^ E 其中(-1)^ S 表示符号位,当 S = 0 为正,当 S = 1 为负,M 表示有效数字,1 <= M < 22 ^ E 表示指数位

//如下所示符号位   有效数字	   指数
(-1)  ^   S   *   M  *  2 ^  E

举例,十进制的 7.0,二进制表示为 111.0,相当于1.11 * 2 ^ 2。那么,按照上面的格式,7 大于 0,S = 0;有效数字为 1.11,M=1.11;指数为 2,E = 2

再举个例子,对于十进制数 5.5,把它化成二进制数,为 101.1。5.5大于0,S=0;有效数字为 1.011,M = 1.011;指数为 2 E = 2

IEEE 754 规定

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

image-20241206134320169

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

image-20241206134347387

对于有效数字 M 和指数 E,还有一些特别规定

对于 M

由于 M 是范围在 1 和 2 之间,计算机在保存 M 时,默认这个数的第一位总是 1,因此可以舍去,只保存后面的小数部分。而在读取M的时候,会自动加上这个 1,这样的话可以节省一位有效数字,扩大保存数字的范围

例 M 为 1.01 时,假如没有舍去 1,可以保存23位有效数字;将 1 舍去,只保存后面的 01,等于可以保存 24 位有效数字

对于 E

由于 E 为一个无符号整数(unsigned int)没有负数,但浮点型数据的指数是可以为负的。因此存储 E 的真实值必须加上一个中间数 127(32 位平台下),这样若本身 E 为正,使用时减去 127 即可;若 E 本身为负,加上 127 为正数可以存储,使用时减去 127 即可

当 E 不全为 0 或不全为 1

指数 E 计算值减去 127(或 1023)得到真实值, 再将有效数字 M 前加上第一位的 1

当 E 全为 0

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

注 : 有效数字 M 不再加上第一位的 1,因为当 E 全为 0 时,该小数的范围在 (-1,1)之间

当 E 全为 1

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

例存储十进制的 5.5

image-20241206134854999

补充知识:一个十进制的浮点数据怎么表示成二进制形式
核心要点:整数部分除 2 取余,小数部分乘 2 取整
举一个简单的例子:
178.125整数部分除 2 取余,直至得到 0
178 除 2 得 89,余 0
89  除 2 得 44,余 1
44  除 2 得 22,余 0
22  除 2 得 11,余 0
11  除 2 得 5, 余 1
5   除 2 得 2, 余 1
2   除 2 得 1, 余 0
1   除 2 得 0, 余 1
得到的余数从下往上排列得 10110010,即为 178 的二进制表示小数部分乘 2 取整
0.125 乘 2 得 0.25,整 0
0.25  乘 2 得  0.5,整 0
0.5   乘 2 得  1.0,整 1
得到的整数从上往下排列得 001,即 0.001 为 0.125 的二进制表示
合并上面的两点 178.125 的二进制表达方式为:10110010.011
练习题
/*
9 = 00000000 00000000 00000000 00001001
-> 转成 float 形式
->  0(S) 00000000(E) 00000000000000000001001(M)
->  (-1)^S * M (0.00000000000000000001001) * 2^E(-126)
-> %f 输出小数点后六位 0.000000把对应的 9 的内存空间转为 9.0 表示
-> 1001.0 = 1.0010 * 2^3
-> 转成 float 形式 
-> (-1)^S(0) * M (00100000000000000000000) * 2^E(130)
-> 0 10000010 00100000000000000000000
-> 二进制转十进制 1091567616
*/
int main()
{int n = 9;float* pFloat = (float*)&n;printf("n的值为 %d\n",n); //9printf("*pFloat的值为 %f\n", *pFloat); //0.000000*pFloat = 9.0;printf("n的值为 %d\n", n); //1091567616printf("*pFloat的值为 %f\n", *pFloat); //9.0
}

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

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

相关文章

工作:SolidWorks从3D文件导出2D的DWG或DXF类型文件方法

工作&#xff1a;SolidWorks从3D文件导出2D的DWG或DXF类型文件方法 SolidWorks从3D文件导出2D的DWG或2D DXF类型文件方法&#xff08;一&#xff09;打开3D文件&#xff08;二&#xff09;从装配体到工程图&#xff08;三&#xff09;拖出想要的角度的图型&#xff08;四&#…

Linux-PWM驱动实验

在裸机篇我们已经学习过了如何使用 I.MX6ULL 的 PWM 外设来实现 LCD 的背光调节&#xff0c;其实在 Linux 的 LCD 驱动实验我们也提到过 I.MX6ULL 的 PWM 背光调节&#xff0c;但是并没有专门的去讲解 PWM 部分&#xff0c;本章我们就来学习一下 Linux 下的 PWM 驱动开发。 PWM…

Mysql学习-Mysql查询(1)

1.基本查询&#xff08;SELECT&#xff09; SELECT语句基本格式&#xff1a; SELECT {*|<字段列表>} [ FROM<表1>&#xff0c;<表2>.. [WHERE <表达式> [GROUP BY<group by definition>] [HAVING <expression>[{<operator><exp…

深入解析ETL与ELT架构:数据集成技术的演进与发展

摘要&#xff1a;随着大数据时代的到来&#xff0c;数据集成成为企业信息化建设的重要环节。本文将深入探讨ETL与ELT两种架构&#xff0c;分析它们在数据处理、性能、可扩展性等方面的差异&#xff0c;为企业数据集成提供技术指导。 一、引言 在大数据时代&#xff0c;企业需要…

探索自然语言处理奥秘(NLP)

摘要 自然语言处理&#xff08;NLP&#xff09;是人工智能领域的一个重要分支&#xff0c;它致力于使计算机能够理解、解释和生成人类语言。这项技术让机器能够阅读文本、听懂语音&#xff0c;并与人类进行基本的对话交流。 通俗理解 自然语言处理&#xff08;NLP&#xff09…

product/admin/list?page=0size=10field=jancodevalue=4562249292272

文章目录 1、ProductController2、AdminCommonService3、ProductApiService4、ProductCommonService5、ProductSqlService https://api.crossbiog.com/product/admin/list?page0&size10&fieldjancode&value45622492922721、ProductController GetMapping("ad…

Appium:安装uiautomator2失败

目录 1、通过nmp安装uiautomator2&#xff1a;失败 2、通过 Appium 的平台直接安装驱动程序 3、通过pip 来安装 uiautomator2 1、通过nmp安装uiautomator2&#xff1a;失败 我先是通过npm安装的uiautomator2&#xff0c;也显示已经安装成功了&#xff1a; npm install -g …

【Golang】Go语言编程思想(二):函数式编程

函数式编程 函数与闭包 支持函数式编程的语言当中&#xff0c;函数是一等公民&#xff0c;参数、变量、返回值都可以是函数。 以 adder 为例&#xff0c;下例实现了一个函数式编程&#xff1a; package mainimport "fmt"func adder() func(int) int {sum : 0retu…

摄影后期学什么_好学吗?

当你按下相机快门&#xff0c;捕捉到那珍贵的瞬间&#xff0c;摄影可还没画上句号哦&#xff01;摄影后期就像是一场神奇的魔法秀&#xff0c;能让你的照片从平凡瞬间变身惊艳大片。那在这场魔法之旅中&#xff0c;咱们得学习哪些厉害的法术呢&#xff1f; 先来说说光影调整这…

windows11 安装nginx 教程(并开机自启动)

windows11 安装nginx 教程&#xff08;并开机自启动&#xff09; 1、官网下载安装 官网地址&#xff1a;http://nginx.org/en/download.html 2. 找个文件位置解压压缩包 3 启动NGINX 4 相关命令 查詢端口被占用的进程 netstat -ano | findstr "80"1、查看nginx的…

如何在谷歌浏览器中启用双重身份验证

在当今数字化时代&#xff0c;保护个人信息的安全变得尤为重要。双重身份验证&#xff08;2FA&#xff09;是一种增强账户安全性的有效方法&#xff0c;通过要求用户在输入密码之外&#xff0c;还需提供第二种身份验证信息&#xff0c;从而大大降低了账户被非法访问的风险。本文…

高级 CEF 内核集成与 VC++——CEF系统架构与开发环境搭建

目录 1. 系统架构总体设计&#xff1a;CEF 内核与 VC 主程序分层架构设计 1.1 多进程架构设计 1.2 主程序与 CEF 内核的分离 1.3 UI 设计与布局 1.4 性能与资源管理 2. 分进程设计与通信机制&#xff1a;渲染进程与主进程分离&#xff0c;保证安全性与稳定性 2.1 分进程…

RTCMultiConnection 跨域问题解决

js套件地址 https://github.com/muaz-khan/RTCMultiConnection server套件地址 https://github.com/muaz-khan/RTCMultiConnection-Server 要解决的就是server代码的跨域问题 原装写法&#xff1a; 解决写法&#xff1a; // 喜欢组合语法的自己组 const io new ioServer.S…

【Flink】Flink Checkpoint 流程解析

Flink Checkpoint 流程解析 Checkpoint 流程解析 Flink Checkpoint 流程解析Checkpint 流程概括Checkpoint 触发流程解析 (Flink 1.20)任务启动后 JobManager 开始定期对任务执行 CheckpointJobManager 使用 CheckpointCoordinator 触发 CheckpointCheckpointCoordinator 初始化…

Redis探秘Sentinel(哨兵模式)

概述 Redis的高可用机制有持久化、复制、哨兵和集群。其主要的作用和解决的问题分别是&#xff1a; 持久化&#xff1a;持久化是最简单的高可用方法(有时甚至不被归为高可用的手段)&#xff0c;主要作用是数据备份&#xff0c;即将数据存储在硬盘&#xff0c;保证数据不会因进程…

3GPP R18 LTM(L1/L2 Triggered Mobility)是什么鬼?(三) RACH-less LTM cell switch

这篇看下RACH-less LTM cell switch。 相比于RACH-based LTM,RACH-less LTM在进行LTM cell switch之前就要先知道target cell的TA信息,进而才能进行RACH-less过程,这里一般可以通过UE自行测量或者通过RA过程获取,而这里的RA一般是通过PDCCH order过程触发。根据38.300中的描…

波特图方法

在电路设计中&#xff0c;波特图为最常用的稳定性余量判断方法&#xff0c;波特图的根源是如何来的&#xff0c;却鲜有人知。 本章节串联了奈奎斯特和波特图的渊源&#xff0c;给出了其对应关系和波特图相应的稳定性余量。 理论贯通&#xff0c;不在于精确绘…

【React】React常用开发工具

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、React DevTools二、Redux DevTools三、Create React App 前言 React 是一种用于构建用户界面的流行 JavaScript 库&#xff0c;由于其灵活性、性能和可重用…

C++知识整理day4内存管理——new和delete详解

文章目录 1.C/C内存分布2.C语言中动态内存管理&#xff1a;malloc/realloc/calloc3.C内存管理方式3.1 new/delete操作内置类型3.2 new和delete操作自定义类型 4.malloc/free和new/delete到底什么区别&#xff1f;4.1 对于自定义类型4.2 对于自定义类型4.3 总结&#xff1a;它们…

flex: 1 display:flex 导致的宽度失效问题

flex: 1 & display:flex 导致的宽度失效问题 问题复现 有这样的一个业务场景&#xff0c;详情项每行三项分别占33%宽度&#xff0c;每项有label字数不固定所以宽度不固定&#xff0c;还有content 占满标签剩余宽度&#xff0c;文字过多显示省略号&#xff0c; 鼠标划入展示…