重生之我在异世界学编程之C语言:数据在内存中的存储篇(上)

大家好,这里是小编的博客频道

小编的博客:就爱学编程
很高兴在CSDN这个大家庭与大家相识,希望能在这里与大家共同进步,共同收获更好的自己!!!

目录

引言

本文主要讲述了数据在内存中的存储位置,存储方法以及如何读取内存中的数据。如果你对其有困惑,不妨好好阅读,也许会有新的体会和感悟。


那接下来就让我们开始遨游在知识的海洋!

正文


一、数据类型介绍

1.内置类型

整型家族
家族成员 int char unsigned.int short.int long.long unsigned.short signed.short unsigned.long.long signed.long.long signed.int unsigned.char signed.char

疑惑点:为什么char类型属于整型家族?
解释:这是因为char类型的数据在存储的时候,是以它的ASCLL值进行存储的,本质是一个整型。

浮点型家族
家族成员 float double

注意:浮点型都是有符号的。


2.自定义类型

组成 数组 结构体.struct 枚举.enum 联合体.union


3.指针类型

组成 一级指针 用来存放内置数据类型变量的地址 二级指针 用来存放一级指针变量的地址


4.空类型(void)

常用于表示函数不需要参数

例:

c 复制代码
#include<stdio.h>
int main(void) {
	printf("hello,world!");
	return 0;
}

运行结果:

当然,这常适用于非主函数


二、数据在内存中的存储规则

符号位:对于一个有符号的的数来说,最高位就是符号位,c语言中规定1表示负,0表示正。

1.整型数据在内存中的存储

原码 、反码 、补码

原码

把一个十进制数直接转化为它的二进制,就是这个数的原码。
反码
正数:还是原码
负数:符号位不变,其他位按位取反

补码
正数:还是原码
负数:反码+1

signed.int 负数.十进制 原码.二进制 符号位不变,其他位按位取反 反码 加1 补码 正数.十进制

我们要注意:

  • 对于有符号整型数据:只有屏幕上打印的是原码,而在内存中进行存储和表示的都是补码。

为什么?

序号 原因
1. 使用补码,可以将符号位和数值域统一处理
2. 加减法也可以统一用加法处理(CPU只有加法器)

凭什么这么说?我们可以用一个简单的例子佐证我们的说法

例:

  • 写一个代码打印-1+1的结果

代码:

c 复制代码
#include<stdio.h>
int main() {
	printf("%d", -1 + 1);
	return 0;
}

运行结果:

思考 讨论
到这里,你可能会想:这不就理应如此吗? 但事实上,这是我们c语言设计好的用补码进行运算的结果。不信?你看:如果我们用原码进行计算,我们会惊奇的发现发现:最后的打印结果应为-2。但是这就与常理违背了,-1 + 1 == -2 , 离之大谱!

所以通过这个例子,我们就不难发现:

  • 对于整型数据来说,只有屏幕上打印的是原码,而在内存中进行存储和表示的都是补码的智慧和原因
序号 注意
1. 原码,反码,补码的概念主要针对有符号的整型家族(signed int或int)类型的数据,而对于其他类型的数据,有其相应的存储方式
2. 特殊地:我们把无符号的整型家族可以看做正数(有符号的数)进行处理,只不过这个正数没有符号位

2.整型数据的运算与打印

但是,掌握了以上的知识点之后,我们其实还并不能完全预见和理解整型数据运算屏幕上打印的结果,还得掌握一些占位符 的作用,整型提升数据截断数据范围

(1)占位符
占位符 作用
%d 打印有符号整型数据
%u、%zd 打印无符号整型数据

例:

c 复制代码
#include<stdio.h>
int main() {
	int a = 2147483647;
	int b = 1;
	printf("%d %u", a + b, a + b);
	return 0;
}

运行结果:

思考:为什么同样是a + b,打印的结果确一正一负呢?

  • 这里其实涉及了后文要讲的数据范围,但我们通过这个例子依旧是能够感受到占位符不同所带来的不同打印结果,该题我们在后文再进一步剖析。

(2)数据范围

我们知道:

1.每个数据类型都有其对应的字节长度,1字节 == 8bit 位

详见:

数据类型 字节
char 1
short(int) 2
(long) int 4
float 4
long long 8
double 8

那这些bit位是用来干嘛的呢?

2.实际上,这些bit位就是用来存放数字1或0来表示数据大小的。由此我们可以想到:那既然表示数据大小的位数是有限的,那每个类型的数据大小也应该是在一个范围内的。

详见:

数据类型 范围
char -128 ~ 127, 0 ~ 255
short(int) - 2^15 ~ 2^15 - 1, 0 ~ 2^16 - 1
(long) int - 2^31 ~ 2^31 - 1, 0 ~ 2^32 - 1

注:有负号的为有符号数据类型数据的范围


那如果一个数据超过了其范围,怎么计算?

我们可以用圆环的思想去考虑:

  • 每一个无符号的数据类型从大到小都是bit位全0-->bit位全1,而到了最大的时候,也就是bit位全1,再加1就会发生数据越位,所有bit位变成全0,然后再由bit全0-->bit位全1,构成了一个头尾相交的圆环。

以unsigned char为例:
11111100 11111111 00000000

等价于:
12 225 0

而每一个符号的数据类型从大到小都是bit位全0-->bit位除了首位全1,而到了最大的时候,也就是bit位除了首位全1,再加1就会变成bit位除了首位全0,然后再变成bit位全1,再加1就会发生数据越位,所有bit位变成全0,然后再由bit全0-->bit位全1,构成了一个头尾相交的圆环。

但是和无符号数据类型不同的是:

  • 有符号的数它的二进制位比无符号的数少了一位数值位 ,并且我们规定1000...0000000表示的是最小的负数

但我们应注意:

  • 二进制数存储和表示无论是正数还是负数都是采用补码的方式,而只是因为我们把无符号数据看做正数,原码和补码一样,且多了一位数值位才导致了表示和存储方式看起来不同,实际都是一样用补码的形式进行存储和表示。
无符号和有符号数的异同 详述
不同 无符号数多了一位数值位
相同 实际都是一样用补码的形式进行存储和表示
重要规定(有符号的数) 1000...0000000表示的是最小的负数

以signed char为例:
10000000 11111111 00000000 01111111

等价于:
127 0 -128 -1

了解这些,我们就可以把上面的例子剖析

例子:

c 复制代码
#include<stdio.h>
int main() {
	int a = 2147483647;
	int b = 1;
	printf("%d %u", a + b, a + b);
	return 0;
}

运行结果:

疑惑 剖析
为什么用占位符%d打印的结果是负数? (1)十进制:2147483648-->二进制(补码):01111111111111111111111111111111;十进制:1-->二进制(补码):00000000000000000000000000000001;相加得:10000000000000000000000000000000(补码)(2)又用%d进行打印,%d是用来打印int类型数据的,所以我们要把相加得的结果转换为原码打印出来(这里要用重要规定)
为什么用占位符%u打印的结果是正数? (1)同上;(2)又用%u进行打印,%u是用来打印无符号整数的,无符号整数的特别之处------就在于我们是把它看作有符号整数中的正数,就导致:它的原码和反码是一样的,所以打印在屏幕上的数就是相加结果的十进制数
对于占位符的思考 占位符给了我们一个理解和读取二进制数(补码)的视角和方式

(3)整型提升
问题 答案
什么叫整型提升? c语言的整型算术运算总是至少缺省(默认)整型类型的精度进行计算 。为了获得这个精度,表达式中字符型(char)和短整型(short)在使用之前就被系统自动转换为普通整型(int) ,这种转换就叫整型提升
整型提升的意义是什么? 表达式的整型运算要在CPU的加法器和其他运算器件中进行,而该运算器的操作数的字节长度就为int的字节长度
数据提升的条件和对象 发生在小于整型类型的数据类型上

提升规则
对象 有符号数 正数 高位补0 无符号数 看补码 最高位为1则高位补1.否则补0 负数 高位补1

本质就是看补码的最高位是1还是0,是1补1,是0补0


(4)数据截断
问题 答案
什么是数据截断? 就是一个字节长度较小的数据类型在接收一个字节长度较大的数据类型时只会从字节长度较大的数据的数据低位开始接收,直至填满自己的所有二进制位
会有什么结果? 可能会导致数据的丢失,一定会导致字节的减少

熟练掌握这些,我们就基本能预见和理解整型数据运算屏幕上打印的结果,下面就以一些例子来加深和巩固对以上知识的理解

例:

c 复制代码
#include<stdio.h>
int main() {
	char a = 3;
	//00000011
	char b = 127;
	//01111111
	char c = a + b;
	//因a和b参加运算,所以要发生整型提升
	//整型提升规则:高位补符号位(在大部分编译器中,char等价于signed char,即有符号,最高位为符号位,范围为:-128~127;无符号的char类型数据范围为:0~255;
	//整型提升:
	//00000000000000000000000000000011
	//00000000000000000000000001111111
//结果00000000000000000000000010000000
	//截断(发生在高字节类型转化为低字节):
	//10000000              补码
	//10000011
	printf("%d", c);
	//打印结果为:-126
	return 0;
}
//也可以用圆环秒解:0 1 2 ~ 126 127 ~-1 -2 -3 ~ -127 -128 ~ 0 1 2 ~~
  • 注释的解析很全,不用再过多解释,对照上面的知识点进行理解

三、大小端介绍

1.来历

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


2.模式介绍及效果

模式(全称) 效果
大端字节序存储 数据的低位字节存放在高地址
小端字节序存储 数据的低位字节存放在低地址

实际效果:

自己模拟


VS2022上的内存窗口


3.模式判断

我们可以借助一个2015年百度的笔试题观察大小端的区别

题目:写一个代码,实现大小端的判断

代码:

c 复制代码
#include<stdio.h>
int main() {
	int a = 1;
	char* p = (char*)&a;
	if (*p == 1) {
		printf("是小端\n");
	} else {
		printf("是大端\n");
	}
	return 0;
}

思路:

序号 步骤
1 用char*类型的指针一个一个字节地访问int类型变量a的各个字节
2 我们可以把a赋值为只有第一个字节有值的整数,这里赋值为1
3 前提:我们知道指针访问内存的习惯是从低地址到高地址
4 如果*p == 1,说明低地址存放的字节是低位字节,也就是小端存储
5 如果*p == 0,说明低地址存放的字节是高位字节,也就是大端存储

快乐的时光总是短暂,咱们下篇博文再见啦!!!不要忘了,给小编点点赞和收藏支持一下,在此非常感谢!!!

相关推荐
AAA.建材批发刘哥12 分钟前
Linux快速入门-Linux文件系统管理
linux·运维·服务器·c语言·学习方法
AC使者34 分钟前
5820 丰富的周日生活
数据结构·算法
无 证明1 小时前
new 分配空间;引用
数据结构·c++
Kisorge2 小时前
【C语言】指针数组、数组指针、函数指针、指针函数、函数指针数组、回调函数
c语言·开发语言
爱吃西瓜的小菜鸡5 小时前
【C语言】判断回文
c语言·学习·算法
别NULL5 小时前
机试题——疯长的草
数据结构·c++·算法
ZSYP-S7 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring
唐叔在学习7 小时前
【唐叔学算法】第21天:超越比较-计数排序、桶排序与基数排序的Java实践及性能剖析
数据结构·算法·排序算法
ALISHENGYA7 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(switch语句)
数据结构·算法
FeboReigns8 小时前
C++简明教程(文章要求学过一点C语言)(1)
c语言·开发语言·c++