C语言进阶第一课 -----------深度剖析数据在内存中的存储

作者前言

🎂        ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂

   🎂      作者介绍:                              🎂🎂

       🎂 🎉🎉🎉🎉🎉🎉🎉              🎂

          🎂作者id:老秦包你会,         🎂

简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂

             喜欢学习C语言和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨         🎂🎂🎂🎂🎂🎂🎂🎂

                 🎂个人主页::小小页面🎂

                  🎂gitee页面:秦大大🎂

                   🎂🎂🎂🎂🎂🎂🎂🎂
        🎂  一个爱分享的小博主 欢迎小可爱们前来借鉴🎂

_______________________________________________________

______________________________________________________________________

目录

  • 数据类型详细介绍
  • 整形在内存中的存储:原码、反码、补码
  • 大小端字节序介绍及判断
  •  浮点型在内存中的存储解析

——————————————————————————————————————

数据类型介绍

前面我已经介绍了C语言中 常用的数据类型

char         // 字符数据类型
short       // 短整型
int         // 整形
long         // 长整型
long long   // 更长的整形
float       // 单精度浮点数
double       // 双精度浮点数
这些数据类型在内存中开辟的大小也在前面介绍过了,可以利用sizeof进行测试

类型的基本归类

整形家族:
char
unsigned char
signed char
short
unsigned short [ int ]
signed short [ int ]
int
unsigned int
signed int
long
unsigned long [ int ]
signed long [ int ]
注意一下,字符在内存存储的是ASCII值,ASCII值是整形,所以char归类为整形类型
我们平时写的
int a;
signed int a;
unsigned  int a;
int a  == signed int a
https://blog.csdn.net/m0_69984273/article/details/131998273  这里简单的介绍过了char,signed char  和unsigned char  C语言没有规定,取决于编译器 
浮点数家族:
float
double 

 构造类型(自定义):

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

 指针类型

int * pi ;
char * pc ;
float* pf ;
void* pv ;
空类型:
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型

 如:int main(void)

整形在内存中的存储

int a = 20;

int b = 10;

 会各自在内存开辟四个字节的空间进行存储

其实计算机能够处理的数据是二进制的,整形和浮点型数据在内存也都是以二进制的形式进行存储。

整数的二进制表示形式有3种:原码、反码、补码

正数的原码、反码、补码相同,

负数的反码是符号位不变,其他位取反, 补码是在反码的基础上加1

#include<stdio.h>
int main()
{
	int a = -10;
	//10000000 00000000 00000000 00001010   原码
	//11111111 11111111 11111111 11110101  反码
	//1111 1111 1111 1111 1111 1111 1111 0110  补码 
	// f    f    f    f    f    f    f    6
	//0xfffffff6

	unsigned int b = -10;// 全部位都是有效位,
	//11111111 11111111 11111111 11110110  补码 

	return 0;
}

4个二进制位表示一个十六进制位 

对于整形来说:数据存放内存中其实存放的是补码。
为什么呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统
一处理;
同时,加法和减法也可以统一处理( CPU 只有加法器 )此外,补码与原码相互转换,其运算过程
是相同的,不需要额外的硬件电路。
假设我们利用原码进行加减   如1 +(-1)
00000000 00000000 00000000 00000001
10000000 00000000 00000000 00000001
这里就会无法确定符号位是否相加
如果使用补码相加
00000000 00000000 00000000 00000001
111111111 111111111 111111111 111111111
不管符号位是否相加都不影响结果

大小端介绍(字节大于等于2的数据)

 在内存窗口中就会发现存储的是补码。但是我们发现顺序有点不对劲

大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址
中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位 , ,保存在内存的高地
址中。
例如存储0x11223344

 四个比特位 表示一个十六进制位, 两个十六进制位表示一个字节

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元
都对应着一个字节,一个字节为 8 bit
练习1
设计一个小程序来判断当前机器的字节序
#include <stdio.h>
int check_sys()
{
 int i = 1;
 return (*(char *)&i);
}
int main()
{
 int ret = check_sys();
 if(ret == 1)
 {
 printf("小端\n");
 }
 else
 {
 printf("大端\n");
 }
 return 0;
}
#include <math.h>
int my_end(int a)
{
	int i = 0;
	int sum = 0;
	for (i = 0; i < 8; i++)
	{
		//计算a补码最右端的8位二进制之和
		int b = (a >> i) & 1;
		sum = sum + b * pow(2, i);
	}
	return sum;
}
int main()
{
	int a = 0;
	scanf("%d", &a);
	char* p = (char*)&a;
	//返回*p开始地址指向的那个字节的大小
	int num = my_end(a);
	if (*p = num)
	{
		printf("小端存储");
	}
	else
	{
		printf("大端存储");
	}
	return 0;
}

练习2

#include<stdio.h>
int main()
{
	char a = -1;
	//10000000 00000000 00000000 00000001  原码
	//11111111 11111111 11111111 11111110 反码
	//11111111 11111111 11111111 11111111
	//因为a为字符变量,只能存储一个字节,存储后8位
	//a-- 11111111
	
	signed char b = -1;
	//b -- 11111111
	unsigned char c = -1;
	//c -- 11111111
	printf("%d %d %d", a, b, c);
	//%d是十进制的形式,打印有符号位的整数
	//因为abc三个未够4个字节,发生整形提升
	//a和b 11111111 11111111 11111111 11111111
	//c    00000000 00000000 00000000 11111111
	return 0;
}

注意一下:%d是十进制的形式打印有符号位的整数

#include<stdio.h>
int main()
{
	unsigned char a = -128;
	//10000000 00000000 00000000 10000000
	//11111111 11111111 11111111 01111111
	//11111111 11111111 11111111 10000000
	//a > 10000000
	// 整形提升
	// 11111111 11111111 11111111 10000000  有符号的
	//	00000000 00000000 00000000 10000000  无符号的
	printf("%d\n", a);
	printf("%u", a);


	return 0;
}

赋值后,先整形提升,把整形提升后的值存储进去,对于无符号数,原码、反码、补码是相同的

 char 的范围为-128~127

unsigned char 为0~255

#include<stdio.h>
int main()
{
	int i = -20;
	//10000000 00000000 00000000 00010100
	//11111111 11111111 11111111 11101011
	//11111111 11111111 11111111 11101100
	unsigned int j = 10;
	//00000000 00000000 00000000 00001010
	printf("%d", j + i);
	//11111111 11111111 11111111 11110110
	//因为%d为有符号十进制输出
	// 在%d看来都是有符号的 转换为原码》10000000 00000000 00000000 00001010




	return 0;
}
#include<stdio.h>
#include<windows.h>
int main()
{
	unsigned int i = 0;
	for (i = 9; i >= 0; i--)
	{
		//无符号的整数都大于等于0的
		printf("%u\n", i);
		Sleep(1000);
	}
	return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{
	char arr[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		arr[i] = -1 - i;
	}
	printf("%d", strlen(arr));

	return 0;
}

这道题主要体现出char的范围为-128~127 ,不会超过127

浮点型在内存中的存储

常见的浮点数:
3.14159
1E10
浮点数家族包括: float double long double 类型。
浮点数表示的范围: float.h 中定义

 一这张图为例,可以看出整数的存储形式和浮点数的存储形式是不一样的

浮点数存储规则

根据国际标准 IEEE (电气和电子工程协会) 754 ,任意一个二进制浮点数 V 可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^S 表示符号位,当 S=0 V 为正数;当 S=1 V 为负数。
M 表示有效数字,大于等于 1 ,小于 2
2^E 表示指数位。
十进制的:5.5

 二进制:101.1

利用科学计数法,因为是2进制向左移动两位写成  1.011 * 2^2   ,又因为是正数,所以再乘上(-1)^0

最终写成 (-1)^0 * 1.011* 2^2  即S=0 M=1.011  E=2

所以计算机只要存储S M E就行了

 

 

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 ,情况就比较复杂。
首先, 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。 
include<stdio.h>
int main()
{
	float a = 5.5;
	//101.0
	//(-1)^0 * 1.011 * 2^2
	// S = 0, M = 1.011, E= 2
	// 因为E要加上127后存入即 E= 129
	//0100 0000 1011 0000 0000 0000 0000 0000
	//4      0	  b    0   0    0    0    0  

	return 0;
}

      

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

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 ,则其二进
制表示形式为 :
0 01111110 00000000000000000000000
E 全为 0
这时,浮点数的指数 E 等于 1-127 (或者 1-1023 )即为真实值,
有效数字 M 不再加上第一位的 1 ,而是还原为 0.xxxxxx 的小数。这样做是为了表示 ±0 ,以及接近于
0 的很小的数字。
E 全为 1
这时,如果有效数字 M 全为 0 ,表示 ± 无穷大(正负取决于符号位 s );
好了,关于浮点数的表示规则,就说到这里。
int main()
{
	int n = 9;
	//0000 0000 0000 0000 0000 0000 0000 1001
	float* pfloat = (float*)&n;
	printf("%d\n", n);
	//0 00000000 00000000000000000001001
	// S = 0 E = 1-127 = -126 M = 0.00000000000000000001001
	//(-1)^S * M * 2^E 
	printf("%f\n", *pfloat);

	*pfloat = 9.0;
	// 1001.0
	//(-1)^0 * 1.0010 * 2^3
	// E = 3+127
	//0 10000010 00100000000000000000000 
	printf("%d\n", n);
	printf("%f\n", *pfloat);
	return 0;
}

  • 34
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 27
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老秦包你会

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值