IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C语言从青铜到王者】第五篇·数据在内存中的存储 -> 正文阅读

[C++知识库]【C语言从青铜到王者】第五篇·数据在内存中的存储

本篇前言
从本篇开始,我们要开始逐渐和内存打交道了。想学好C语言,打牢编程基本功,我们心中一定要时刻有内存的概念。

数据类型及其意义

整型与浮点型

char

short

int

long

long long

float

double

构造(自定义)类型

数组类型

结构体类型 struct

枚举类型 enum

联合类型 union

空类型

void

void test (void) 函数返回类型 函数参数

void* p 指针

指针类型

以上所有类型 *

数据类型的意义

数据类型的意义有两个:

1.决定为变量开辟内存空间的大小

2.决定看代内存中0/1序列的视角

这两句话到底是什么意思,相信看完本文你就清楚了


整型数据在内存中的存储

整型家族

char

? unsigned char signed char

short

? unsigned short signed short

int

? unsigned int signed int

long

? unsigned long signed long

为什么char类型是整型家族呢?

因为字符型数据是按照ASCII码值存储在内存中的,而ASCII码值也是整数,所以char类型也是整型家族的一员

整型数据在内存中的存储

任何数据在内存中都是以二进制序列存储。整数的二进制序列有三种形式,分别是原码、反码、补码

整数在内存中是以补码的形式存储的,比如看下面的a:

#include<stdio.h>
int main()
{
	int a = -10;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
为什么-10在内存中存储为f6 ff ff ff的形式呢?

  • 详解:
    -10的原码:
    10000000 00000000 00000000 00001010
    -10的反码:
    11111111 11111111 11111111 11110101
    -10的补码:
    11111111 11111111 11111111 11110110
    换算成16进制:
    ff ff ff f6

为什么内存中存补码而不存原码?

比如我们想计算 1 - 1这个算数

由于计算器底层没有减法器,我们需要用加法器代替减法器

1 - 1 → 1 + (-1)

用原码:

1 :00000000 00000000 00000000 00000001

-1 :10000000 00000000 00000000 00000001

相加:10000000 00000000 00000000 00000010

结果是 -2 ,不符合结果

用补码:

1 : 00000000 00000000 00000000 00000001

-1 :11111111 11111111 11111111 11111111

相加:1 00000000 00000000 00000000 00000000

由于只有32位,首位丢弃:

结果:00000000 00000000 00000000 00000000

结果为0,符合结果

出现这一现象的本质原因是

1.补码可以将二进制数据的符号位和数值位统一处理,可以将加法和减法统一处理

2.补码和原码的相互转换,其运算过程是相同的,不需要额外的电路(原码→取反+1→补码 、补码→取反+1→原码)

这也是为什么密码学家发明补码的一个原因


大小端字节序

刚刚-10的例子的结果同学们一定有疑问:

-10的16进制补码:ff ff ff f6

而编译器中内存值:
在这里插入图片描述
为什么顺序正好反过来了呢?

这就涉及到了大小端字节序的问题

我们把一个内存单元看成一个整体,1字节8bit位,8位二进制即二位16进制,所以两个16进制的数字就表示一字节的大小,也就是一个内存单元的大小。而将这些单元编号(也就是标上地址)的顺序是可以不同的:

大端字节序:高位数字放在高地址(符合我们的阅读习惯)

小端字节序:高位数字反而放在低地址(我的编译器的内存中存储类型)

为什么有大小端?

内存中,每个地址单元都对应一个内存单元,大小为1字节8bit。如果存储的类型都是8bit大小,也就不需要对内存单元进行排序,但是C语言中还有16bit的short类型,32bit的int类型等等超过一个内存单元大小的数据类型,现在流行的32位64位处理器的寄存器宽度也大于一个字节,所以我们不得不面临将多字节安排的问题

我写的判断自己编译器大小端的代码:

#include<stdio.h>
int main()
{
	int a =1;
	char* p = &a;
	if (*p)
		printf("小端");
	else
		printf("大端");
	return 0;
}

image.png

实例操练

题一:请手算下面的程序的输出结果

#include<stdio.h>
int main()
{
	char a = -1;
	signed char b = -1;
	unsigned char c = -1;
	printf("a=%d, b=%d, c=%d", a, b, c);
	return 0;
}

在这里插入图片描述

  • 详解:
    -1的补码:
    11111111 11111111 11111111 11111111
    由于a、b、c三个变量都是一字节
    截断:
    11111111
    则a、b、c变量位置内存值为:
    11111111
    a、b为有符号char,所以最高位为符号位(char是有符号还是无符号,是由编译器决定的,但是大部分编译器是有符号)
    1 1111111
    由于是以%d打印,需要进行整型提升
    a、b整型提升后补码:
    11111111 11111111 11111111 11111111
    打印出原码的十进制:
    -1
    c为无符号char,所以所有位都是数值位
    c 整型提升后补码:
    00000000 00000000 00000000 11111111
    原码:
    00000000 00000000 00000000 11111111
    十进制:
    255

题二:请手动计算下面的程序的输出结果

#include<stdio.h>
int main()
{
	char a = -128;
	printf("%u", a);
	return 0;
}

在这里插入图片描述

  • 详解:
    -128的二进制原码:
    10000000 00000000 00000000 10000000
    反码:
    11111111 11111111 11111111 01111111
    补码:
    11111111 11111111 11111111 10000000
    放入char中发生截断:
    10000000
    由于%u打印,所以整型提升,且无符号位
    11111111 11111111 11111111 10000000
    由于无符号数的原反补码相同
    在这里插入图片描述

总结:

1.数值是以补码在内存中操作(截断、整型提升)的

2.printf中的%d等类型决定的是最后看待内存中补码的角度和是否需要整型提升

题三:请手动计算下面的程序的输出结果

#include<stdio.h>
int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d\n", i + j);
	return 0;
}
  • 详解:
    -20的二进制序列
    10000000 00000000 00000000 00010100
    反码
    11111111 11111111 11111111 11101011
    补码
    11111111 11111111 11111111 11101100
    10的二进制序列
    原反补码
    00000000 00000000 00000000 00001010
    i + j 补码相加
    11111111 11111111 11111111 11110110
    反码
    11111111 11111111 11111111 11110101
    原码
    10000000 00000000 00000000 00001010
    %d为有符号整型,结果为
    -10

题四:请手动计算下面的程序的输出结果

#include<stdio.h>
int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
	return 0;
}

感性的定性判断一下:i是无符号数,无符号数一定大于等于0,所以代码会死循环

  • 详解:
    9的二进制序列
    00000000 00000000 00000000 00001001
    以u%看待,就是9
    同理程序依次打印出87654321
    i = 0时,打印出0
    00000000 00000000 00000000 00000000
    然后i--
    11111111 11111111 11111111 11111111
    由于是无符号数,所以原反补码相同
    它的十进制为:
    232-1
    所以这整个循环就是:
    232-2232-310232-1232-2…(死循环)

题五:请手动计算下面的程序的输出结果

#include<stdio.h>
#include<string.h>
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	return 0;
}

image.png

  • 详解:
    -1的二进制序列补码
    11111111 11111111 11111111 11111111
    放入a[0]截断
    11111111
    原码:
    10000001
    a[0]是-1
    -2的二进制补码
    11111111 11111111 11111111 11111110
    截断后原码
    10000010
    a[1]是-2
    一直到-127
    原码
    10000000 00000000 00000000 01111111
    反码
    11111111 11111111 11111111 10000000
    补码
    11111111 11111111 11111111 10000001
    截断后
    10000001
    原码
    11111111
    a[126]是-127
    -128
    原码
    10000000 00000000 00000000 10000000
    补码
    11111111 11111111 11111111 10000000
    截断
    10000000
    原码的值为-128(特殊序列)
    -129
    原码
    10000000 00000000 00000000 10000001
    补码
    11111111 11111111 11111111 01111111
    截断
    01111111
    原码的值为127
    循环为
    -1 → -128 → 127 → 0
    当a为0时候,就是strlen的结束标志
    所以前面有128+127一共255个元素

char类型存储的数据范围

内存中补码:00000000 → 11111111

正数:00000000 → 01111111 即 0 → 127

负数:11111111 → 1000001 即原码 10000001 → 11111111 即 -1 → -127

特殊补码序列 10000000 由于数值位无法再减去1 所以直接规定 10000000 的原码值为 -128

因为-128本身的原码:
10000000 00000000 00000000 10000000
反码:
11111111 11111111 11111111 01111111
补码:
11111111 11111111 11111111 10000000
装入char类型中发生截断:
10000000
也就是特殊序列10000000
所以char的取值范围是 -128 → 127


浮点型数据在内存中的存储

浮点型家族

float

double

常见的浮点数:

3.1415926

1E10 (1×10^10)

引例

#include<stdio.h>
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,打印出正整数9
但二、三、四的结果就很让人匪夷所思了
下面就来解析为什么会出现这样的结果


浮点型数据是怎么放入内存的

浮点数在内存中的存储是由IEEE(电气电子工程协会)的754标准规定的:

任意一个浮点数可以表示成

(-1)^s * M * 2^E

(-1)^s 表示符号位 s=1就是负数 s=0就是正数

M表示有效数字 1 <= M < 2

E表示指数位

下面详细说一下这是啥意思。
首先我们要学会将小数转换成二进制数。
我们以前都学过科学计数法,这种计算小数的方法其实就是用2作为底数的科学计数法
举例说明怎么转换:
现有浮点数 十进制表示法为 5.5
5的二进制:101(整数除以2,结果的余数作为每一次的结果,除数再除以2,直到除数为0)
0.5的二进制:0.1(小数乘以2,结果的整数位作为每一次的结果,小数再乘以2,直到小数为0)
image.png

所以5.5的二进制表示就是101.1

科学计数法就是 1.011×2^2

则5.5表示成 (-1)^s * M * 2^E 就是

(-1)^0 * 1.011 * 2^2

s=0

M=1.011

E=2

拿到了这三个参数后,我们看看它们是怎么存入内存的

754规定:

对于float类型,一共分配4字节32bit内存

第1位为s位,第2-9位 8bit 为 E,第10-32位 23bit 为M

对于double类型,一共分配8字节64bit内存

第1位为s位,第2-12位 11bit 为 E,第13-64位 52bit 为M

S的值:正数放0 负数放1 与整数的符号位意义相同

M的值:由于M一定是1.xxxxxx的形式,所以1可以不存,只存后面的数字(为了增加存储的有效数字量,增加精度),后面的数字直接顺序排列在M的位置上

E的值:由于E是整数,可正可负 8bit的范围是0-255,11bit的范围是0-2047,所以E的值必须在原来整数的基础上加上127和1023,这样E表示的范围就是-127 → 128和-1023 → 1024

综上,可以知道float和double表示的数字范围

image.png

紧接上文,5.5的三个参数为
s=0
M=1.011
E=2
拿到了这三个参数后,经过处理:
s=0,E=2+127=129=10000001,M=011
所以总序列为
0 10000001 01100000000000000000000
再把二进制换成16进制
0100 0000 1011 0000 0000 0000 0000 0000
40 b0 00 00
再按照小端字节序排序
00 00 b0 40
来见证奇迹吧:

image.png

image.png
实验证明浮点型数据确实是这样存储的


浮点型数据是怎么从内存中拿出来的

我们已经知道了浮点型数据是怎么放入内存中,现在来探讨浮点型数据是怎么拿出来的

浮点数的取出,其实就是存入的反操作,但是有以下不同的情况

情况一:E全为0

指数E的真实值为-127或-1023,此时的浮点数无限接近于0,此时M的值还原时不再+1,表示为很小的数字,显示出来就是0

情况二:E全为1

表示±无穷大

情况三:E不全为0且不全为1

E-127得到E的真实值 M+1得到M的真实值 最后得到真实的浮点数

现在终于可以把引例重新拿过来看看:

#include<stdio.h>
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;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YM6BwtN6-1661741469783)(C:\Users\23867\AppData\Roaming\Typora\typora-user-images\image-20211001164938306.png)]

9的二进制位:
0000000 0000000 0000000 00001001
按浮点型存储划分
0 00000000 00000000000000001001
可见E为全0,符合情况一,所以打印出来是0.000000(后面的位数显示不出来)
而9.0的二进制位:
1.001×2^3 s=0 E=130 M=001
0 10000010 00100000000000000000000
再以有符号整型的十进制读出

image.png

这样第三个结果也就出来了

至此,上面程序结果的解析也就全部完成,浮点数在内存中的存储也讲解完毕


回顾一下,本文重点讲解了整型和浮点型数据在内存中的存储形式。现在我们知道这些数据是如何被存放在内存中的了。从本文可以看出,计算机和人脑的思维方式差别还是很大的,当“人脑”用“电脑”的方式思考问题是不是多少有点“烧脑”呢?

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-04 00:51:43  更:2022-09-04 00:56:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 13:17:12-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码