【C语言】深度剖析数据在内存中的存储

news2024/11/25 0:44:25

简单不先于复杂,而是在复杂之后。

89efcc89ac61428db4d5b6639b2bd948.jpeg

目录

 1. 数据类型介绍

1.1 类型的基本归类 

2.整型在内存中的存储 

2.1 原码、反码、补码 

2.2 大小端介绍 

2.3 练习 

2.3.1 练习1 

2.3.2 练习2 

3.2.3 练习3 

2.3.4 练习4 

2.3.5 练习5 

2.3.6  练习6

2.3.7 练习7 

2.3.8 对 strlen 类型 size_t 的补充 

 3. 浮点型在内存中的存储

3.1 一个例子 

3.2 浮点数存储规则 


 

 1. 数据类型介绍

 基本的内置类型

 

类型的意义:

1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)

2. 如何看待内存空间的视角

1.1 类型的基本归类 

 整型家族:

long long 
  unsigned long long [int]
    signed long long [int]
    
int a; -----> signed int a;(short 、long 、long long 同理 )
    
char 到底是 signed char 还是 unsigned char 是标准未定义的,取决于编译器的实现。

char -  字符的本质是ASCLL码值,是整型。

生活中有些数据是没有负数的:身高、体重、长度等 
unsigned int high;

有正有负的情况下使用 signed int 或 int
符号位代表正负 不是有效位    符号位是0代表正数
                           符号位是1代表负数                            

浮点数家族:

浮点型家族:只要是表示小数就可以使用浮点型
float 的精度低,存储的数值范围较小,double精度高,存储的数据范围更大

构造类型:

自定义类型:我们可以创造出新的类型

//数组类型
int arr[5]; - 类型为int [5]
int arr2[8]; - 类型为int [8]
char arr2[5] - 类型为char [5]

指针类型:

空类型:

void 表示空类型(无类型)

通常应用于函数的返回类型、函数的参数、指针类型

2.整型在内存中的存储 

一个变量的创建是要在内存中开辟空间的,内存的的大小是根据不同的类型决定的。

数据在开辟内存中究竟是如何存储的?

2.1 原码、反码、补码 

数值有不同的表示形式
2进制
8进制
10进制
16进制

十进制的21
0b10101
025
21
0x15

整数的2进制表示也有三种表示形式:
1.正的整数原码、反码、补码相同
2.负的整数原码、反码、补码是计算得来的
原码:直接通过正负的形式写出二进制序列
反码:符号位不变,其他位按位取反得到的就是反码
补码:反码+1就是补码

int main()
{
int a = 20;
20
000000000000000000000000000010100
ox00 00 00 14
000000000000000000000000000010100
000000000000000000000000000010100
int b = -10;
100000000000000000000000000001010 - 原码
ox80 00 00 0a
111111111111111111111111111110101 - 反码
0xff ff ff f5
111111111111111111111111111110110 - 补码
0xff ff ff f6

return 0;
}

 

内存中本质存放的是二进制,展示的形式是十六进制。

 对于整型来说数据存放内存中其实存放的是补码的二进制序列。

在计算机系统中,数值一律用补码来表示和存储。

原因在于,使用补码,可以将符号位数值域统一处理

同时,加法和减法也可以统一处理(CPU只有加法器)

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

2.2 大小端介绍 

什么是大端小端:

大端【字节序】存储:

是指数据的低位【字节序】的内容保存在内存的高地址中,而数据的高位【字节序】的内容,保存在内存的低地址中。

小端【字节序】存储:

是指数据的低位【字节序】的内容保存在内存的低地址中,而数据的高位【字节序】的内容,保存在内存的高地址中。

注:在计算机中,【字节序】指的是在内存中多字节数据(如整数、浮点数等)的存储顺序。

对于多字节数据(如整数、浮点数等),存储顺序通常被划分为高位字节和低位字节。

高位字节是指数据中最高有效位所在的字节,而低位字节则是指数据中最低有效位所在的字节。

例如,对于16位的整数0xABCD,其中高位字节是0xAB,低位字节是0xCD。

地址是指计算机内存中的一个标识符,它用来表示内存中某个字节的位置。

高地址通常指内存中的较大位置,而低地址则是指内存中的较小位置。

为什么会有大端和小端?

为什么会有大小端模式之分呢?

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。

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

例如:

一个 16bit 的 short 型x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为 高字节, 0x22 为低字节。

对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。

小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 (是一种集成开发环境和编译器)则为大端模式。很多的ARM,DSP(二者都是处理器的类型)都为小端模式。有些ARM处理器还可以 由硬件来选择是大端模式还是小端模式。

 百度2015年系统工程师笔试题:

请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。

(10分)

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	int a = 1;
	if (*(char*)&a == 1)
	{
		printf("小端");
	}
	else
	{
		printf("大端");
	}


	return 0;
}

首先定义了一个整型变量a并赋值为1,然后使用强制类型转换将a的地址转换为字符型指针。

接着通过解引用字符型指针来访问a的第一个字节,判断系统的字节序。

在小端系统中,最低有效字节存储在最低的内存地址上,因此当程序解引用字符型指针时,会读取到值为1的字节。

而在大端系统中,最高有效字节存储在最低的内存地址上,因此当程序解引用字符型指针时,会读取到值为0的字节。

在C语言中,内存单元的最小单位是字节(byte)。一个整型变量在内存中占用多个字节,具体占用多少字节取决于编译器和系统架构。而字符型变量只占用一个字节。

当将一个整型变量的地址转换为字符型指针时,实际上是将整型变量在内存中的第一个字节的地址转换为字符型指针。

这样做可以直接访问内存中的单个字节,而不是整个整型变量。

写成函数形式:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int check_sys()
{
	int a = 1;
	if (*(char*)&a == 1)
		return 1;
	else
		return 0;
}

int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");

	return 0;
}

这是优化后的结果:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int check_sys()
{
	int a = 1;
	return (*(char*)&a == 1);
}

int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");

	return 0;
}

2.3 练习 

2.3.1 练习1 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	char a = -1;
	signed char b = -1;
	unsigned char c = -1;

	printf("a = %d\nb = %d\nc = %d", a, b, c);

	return 0;
}

这个代码输出什么?

先看一下 signed char 在内存中如何存储:

 

 

 

2.3.2 练习2 

 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	char a = -128;
	printf("%u", a);

	return 0;
}

3.2.3 练习3 

 

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>  
int main()
{
    //-128~127
    char a = 128;
    //00000000000000000000000010000000 - 原码 - 补码
    //截断 - 10000000 - a
    //11111111111111111111111110000000 - 提升
    //因为打印无符号整数,原码补码相同,直接打印
    printf("%u\n", a);
    printf("%d\n", a);
    //11111111111111111111111110000000 - 提升
        //10000000000000000000000001111111 - 反码
    //10000000000000000000000010000000 - 原码 - -128
    return 0;
}

2.3.4 练习4 

 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	int i = -20;
	//100000000000000000000000000010100
	//111111111111111111111111111101011
	//111111111111111111111111111101100 - -20的补码
	//
	unsigned int j = 10;
	//000000000000000000000000000001010 - 10的补码

	printf("%d\n", i + j);

	//按照补码的形式进行运算,最后格式化成为有符号整数
	//111111111111111111111111111101100 - -20的补码
	//000000000000000000000000000001010 - 10的补码
	//111111111111111111111111111110110 - 补码
	//100000000000000000000000000001001
	//100000000000000000000000000001010 -> -10

	return 0;
}

2.3.5 练习5 

 

 

unsigned int类型占用4个字节的内存空间,它的取值范围是0到4294967295,当循环变量i递减到0时,它的值已经达到了unsigned int类型的最小值0,再次递减时会发生整数下溢,即数值会从0变成4294967295(2的32次方-1)。

这是因为当整数类型下溢时,C语言规定它们将“回绕”到该类型的最大值减去1,然后继续减少。

因此,当循环变量i变为0时,下一次循环i--操作会将i从0变为4294967295,然后依次递减1。

可以用以下代码验证这一点:

unsigned int i = 0;
i--;  // i变为4294967295
printf("%u\n", i);  // 输出4294967295

因此,当循环变量i递减到0后,它会继续递减,变成4294967295,然后依次递减1,直到程序异常结束。

2.3.6  练习6

 

 

 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>

int main()
{
	char a[1000];
	int i;

	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	//
	//arr[i] --> char  -128~127
	//-1 -2 -3 -4 ····· -128 127 126 ······3 2 1 0 -1····

	printf("%d", strlen(a));
	//strlen 是求字符串长度,关注的是字符串中'\0'(数字0)之前出现多少字符

	return 0;
}

2.3.7 练习7 

 

 unsigned char 的取值范围是0~255,所以 i <= 255 的条件恒成立,就构成了死循环。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

unsigned char i = 0;

int main()
{
	for (i = 0; i <= 255; i++)
	{
		printf("hello,world\n");
	}
	return 0;
}

2.3.8 对 strlen 类型 size_t 的补充 

 

 

 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>

int my_strlen(const char* str)
{
	assert(str);
	int count = 0;
	while (*str)
	{
		str++;
		count++;
	}
	return count;
}

int main()
{
	if (my_strlen("abc") - my_strlen("abcdef") > 0)
	{
		printf(">");
	}
	else
	{
		printf("<");
	}

	return 0;
}

 3. 浮点型在内存中的存储

常用的浮点数 :

 

 

3.1 一个例子 

 

3.2 浮点数存储规则 

 

 

 

 

 

指数 E 的存储分析完,我们还会由把 E 从内存中取出的需求。

指数 E 从内存中取出还可以再分成三种情况。 

1. E不全为0或不全为1

  

这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将 有效数字M前加上第一位的1。

比如:

0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为 1.0*2^(-1),其阶码为-1+127=126,表示为 01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:

 

2. E为全0

这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。

这样做是为了表示±0,以及接近于0的很小的数字。

3. E为全1

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

会了以上这些就可以对刚开始的那段代码做出解释

 

 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	int n = 9;
	//00000000000000000000000000001001 - 补码
	//0 00000000 00000000000000000001001
	//E=-126
	//M = 0.00000000000000000001001
	//+0.00000000000000000001001*2^-126
	//
	float* pFloat = (float*)&n;

	printf("n的值为:%d\n", n);//9
	printf("*pFloat的值为:%f\n", *pFloat);//0.000000

	*pFloat = 9.0;
	//1001.0
	//1.001*2^3
	//S=0  E=3  M=1.001
	//01000001000100000000000000000000
	printf("num的值为:%d\n", n);//1091567616
	printf("*pFloat的值为:%f\n", *pFloat);  //9.0

	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux jstat 简介

本文目录一览&#xff1a; 1、Linux使用jstat命令查看jvm的GC情况2、linux怎么监控 jvm内存 jstat3、Linux系统监控要用到哪些命令4、linux上如何安装jstatd服务 Linux使用jstat命令查看jvm的GC情况 Linux 使用jstat命令查看jvm的GC情况 命令格式 jstat命令命令格式&#…

【Linux】3. 基本权限与文件指令

1. 用户概念 sudo提权配置 2. 权限的定义 3. 文件权限 角色和文件属性是一一对应的关系&#xff1a; 拥有者-所属组-其他人 rwx - rwx - rwx 4. 文件类型 5. chmod 修改文件属性 6. chown/chgrp 修改文件所属角色 7. umask 8. file指令 9. 目录的权限 10. 粘滞位 首先要明确…

Arduino ESP8266 基于本地搭建MQTT服务运行

Arduino ESP8266 基于本地搭建MQTT服务运行 📌相关篇《Arduno ESP8266接入OneNET实时显示DHT11数据》 🔨本地架设MQTT服务端软件:EMQX:https://www.emqx.io/zh/downloads?os=Windows - 🔧MQTT本地客户端软件:mqttx:https://mqttx.app/ 📺服务端接收设备段上传的数据…

人群计数:技术难点、商业产品化成功案例、现状、传统做法、硬件设备、

现状&#xff1a; 比较成熟了&#xff0c;准确率已经很高了&#xff08;有多高&#xff0c;后面我搞懂metrics高到什么程度&#xff0c;再汇报过来&#xff09; 商业公司基本把这个领域做的很透彻了&#xff0c;performance基本到了一一个无法提高的位置了&#xff08;和图像…

卡尔曼滤波器简介——多维卡尔曼滤波

原文&#xff1a;多维卡尔曼滤波 (kalmanfilter.net) 目录 前言 基本背景 状态外推方程 示例 - 飞机 - 无控制输入 示例 - 带控制输入的飞机 示例 – 坠落物体 状态外推方程维度 线性时不变系统 线性动态系统建模 状态外推方程的推导 状态空间表示形式 示例 - 等速…

大数据Doris(六):BE部署及启动

文章目录 BE部署及启动 一、上传安装包并解压 二、修改be.conf 配置文件 三、上传apache-doris-java-udf 对应 jar 四、启动BE 五、将 node3 BE 安装包发送其他 BE 节点 六、配置其他 BE 节点 七、启动其他 BE 节点 BE部署及启动 本集群中我们在node3、node4、node5上…

C S S

目录 1.样式定义方式 1.1行内样式表 1.2内部样式表 1.3外部样式表 2.注解 3.选择器 3.1标签选择器 3.2 id选择器 3.3 类选择器 3.4 派生选择器 3.5 伪类选择器 链接伪类选择器&#xff1a; 位置伪类选择器&#xff1a; ​编辑 目标伪类选择器&#xff1a; 复合选…

数据治理在学术上的发展史以及未来展望

数据治理是大数据领域中非常重要的一环&#xff0c;从早期的学术研究到如今的各大企业落地实践&#xff0c;经历了漫长的过程&#xff0c;数据治理的实践落地本身也是一场马拉松。 从百度学术通过精确关键词匹配&#xff0c;搜索中文期刊的“数据治理” 和外文期刊的“data gov…

Apache安装与基本配置

1. 下载apache 地址&#xff1a;www.apache.org/download.cgi&#xff0c;选择“files for microsoft windows”→点击”ApacheHaus”→点击”Apache2.4 VC17”&#xff0c;选择x64/x86&#xff0c;点击右边download下面的图标。 2. 安装apache &#xff08;1&#xff09;把…

flutter集成Mob推送(Android)

Mob推送 Flutter对接文档 1、在pubspec.yaml文件中加入下面依赖 mobpush_plugin: ^1.2.2 # MOB推送2、导入 MobPush 相关依赖 在项目根目录的build.gradle中添加以下代码&#xff1a; buildscript {repositories {// 配置Mob Maven库maven {url "https://mvn.mob.com/an…

4月份读书学习好文记录

4月份学习记录 找到自己感兴趣的方向&#xff0c;而不是人云亦云&#xff0c;知道自己想要的是什么&#xff0c;而不是一直得过且过&#xff01; 差距是怎么出现的&#xff0c;四年来的点点滴滴&#xff01;&#xff01;&#xff01; 一个前端大佬的十年回顾 | 漫画前端的前世…

Confidential Containers发布0.5.0版本,龙蜥将基于八大特性构建开箱即用的机密容器解决方案

文/段勇帅 01 前言 机密容器&#xff08;Confidential Containers&#xff0c;简称CoCo&#xff09;是 Cloud Native Computing Foundation&#xff08;CNCF&#xff09;Sandbox 项目。目前机密容器项目的核心参与者包括阿里云、AMD、ARM、IBM、Intel、Microsoft、Red Hat、R…

OpenGL(三)——着色器

目录 一、前言 二、Shader 2 Shader 2.1 顶点着色器 2.2 片段着色器 三、APP 2 Shader 四、顶点颜色属性 五、着色器类C 一、前言 着色器Shader是运行在GPU上的小程序&#xff0c;为图形渲染管线的某个特定部分而运行。各阶段着色器之间无法通信&#xff0c;只有输入和输…

uniapp 截图或者生成海报

需求&#xff1a;uniapp移动端需要生成一张当前界面的海报 方案一&#xff1a;类似于手机按钮截图效果。实现代码如下&#xff1a; doSaveScreen() { let $this this; uni.showLoading({ //加载框 title: 保存中..., …

Sqlmap手册—史上最全

Sqlmap手册—史上最全 一.介绍 开源的SQL注入漏洞检测的工具&#xff0c;能够检测动态页面中的get/post参数&#xff0c;cookie&#xff0c;http头&#xff0c;还能够查看数据&#xff0c;文件系统访问&#xff0c;甚至能够操作系统命令执行。 检测方式&#xff1a;布尔盲注、…

css中的background属性

文章目录 一&#xff1a;background-repeat二&#xff1a;background-position三&#xff1a;background缩写方式三&#xff1a;background-size四&#xff1a;background-origin五&#xff1a;background-clip 在日常前端开发中&#xff0c;经常需要进行背景或背景图的处理。但…

国民技术N32G430开发笔记(10)- IAP升级 Application 的制作

IAP升级 Application 的制作 1、App程序跟Bootloader程序最大的区别就是&#xff0c; 程序的执行地址变成了之前flash设定的0x08006000处&#xff0c; 大小限制为20KB 所以修改Application工程的ld文件 origin 改成 0x08006000 length 改成0x5000 烧录是起始地址也要改为x0x…

【chapter30】【PyTorch】[动量与学习率衰减】

前言&#xff1a; SGD的不足&#xff1a; ①呈“之”字型&#xff0c;迂回前进&#xff0c;损失函数值在一些维度的改变得快&#xff08;更新速度快&#xff09;&#xff0c;在一些维度改变得慢&#xff08;速度慢&#xff09;- 在高维空间更加普遍 ②容易陷入局部极小值和鞍点…

JVM性能调优监控工具jps、jstack、jmap、jhat、jstat

JDK本身提供了很多方便的JVM性能调优监控工具&#xff0c;除了集成式的VisualVM和jConsole外&#xff0c;还有jps、jstack、jmap、jhat、jstat等小巧的工具&#xff0c;本博客希望能起抛砖引玉之用&#xff0c;让大家能开始对JVM性能调优的常用工具有所了解。 现实企业级Java开…

【数据架构系列-06】一文搞懂数据模型的3中类型——概念模型、逻辑模型、物理模型

数据模型就是模拟现实世界的方法论&#xff0c;是通向智慧世界的基石&#xff01; 从现实世界发展到智慧世界&#xff0c;要数经历现实世界、信息世界、计算机世界、数据世界、智慧世界五个不同的世界&#xff0c;我们天生具有从混沌的世界抽象信息变为信息世界的能力&#xff…