幽微之处见真章:数据类型与内存存储的内在联系

嘿嘿,家人们,今天咱们来深度剖析数据类型在内存中的存储,好啦,废话多不讲,开干!


1.:数据类型介绍

1.1:类型的归类

1.1.1:整型家族

1.1.2:浮点数家族

1.1.3:构造类型

1.1.4:空类型

2:整数在内存中的存储

3:大小端字节序和字节序判断

3.1什么是大小端

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

3.3:判断一个机器的字节序

4:相关练习

4.1:练习1

4.2:练习2

4.3:练习3

4.4:练习4

4.5:练习5

4.6:练习6

5:浮点数在内存中的存储

5.1:代码1

5.2:浮点数存储规则

5.2.1:例子1

5.2.2:例子2

5.2.3:IEE754规定

5.3:浮点数存储的过程

5.4:浮点数取的过程

5.4.1:E不全为0或不全为1

5.4.2:E为全0

5.4.3:E为全1

6:题目解析


1.:数据类型介绍

在前面呢,博主已经介绍了基本的数据类型:

char                 //字符数据类型                  ---->占据1个字节
short               //短整型                             ---->占据2个字节
int                    //整形                                 ---->占据4个字节
long                //长整型                               ---->占据4个字节
long long        //更长的整形                        ---->占据8个字节
float                //单精度浮点数                    ---->占据4个字节
double            //双精度浮点数                    ---->占据8个字节
那么这些基本的数据类型,这些类型的意义在哪呢?
  1. 使用这个类型开辟内存空间大小(大小则决定了使用的数值范围).
  2. 为学习者提供了一个看待内存空间的视角.

1.1:类型的归类

1.1.1:整型家族

char

        unsigned char

        signed     char

PS:字符型的数据在内存中是以ascii码值的方式存储的,因此属于整型家族.

 

short

        unsigned short int(无符号短整型)

        signed     short int(有符号短整型)

 

int 

        unsigned int(无符号短整型)

        signed     int(有符号短整型)

 

long

        unsigned lont int(无符号长整型)

        signed            int(有符号长整型)

1.1.2:浮点数家族

float            浮点型

double         双精度浮点型

1.1.3:构造类型

数组类型

结构体类型   struct

枚举类型       enum

联合类型       union

1.1.4:空类型

void 表示空类型(无类型).

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

2:整数在内存中的存储

在操作符的时候,博主有讲解到原码,反码,补码的相关知识,这里带着uu们简单回顾下.

  1. 整数的2进制表⽰⽅法有三种,即 原码、反码和补码
    三种表⽰⽅法均有 符号位和数值位 两部分,符号位都是⽤ 0表⽰“正” ⽤1表⽰“负 ”, 最⾼位的⼀位是被当做符号位 ,剩余的都是数值位.
  2. 正整数的原、反、补码都相同.
  3. 负整数的三种表⽰⽅法各不相同。
    (1): 原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
    (2): 反码: 符号位不变,其他位 依次按位取反就可以得到反码。
    (3): 补码:反码+1就得到补码。
对于整形来说: 数据存放在内存中其实存放的是补码.这是为什么呢
(1):在计算机系统中,数值⼀律⽤补码来表⽰和存储。原因在于: 使⽤补码,可以将符号位和数值域统⼀处理.
(2): 同时,加法和减法也可以统⼀处理( CPU只有加法器 )此外,补码与原码相互转换,其运算过程是 相同的,不需要额外的硬件电路.
这里我们举个几个简单的例子

3:大小端字节序和字节序判断

当我们了解了整数在内存中的存储方式后,接下来我们来通过调试看一个小细节.

通过调试,我们能够清晰地看到,在a中的 0x12345678 这个数字是按照字节为单位,倒着存储的。这是为什么呢?这里就要涉及到大小端存储啦

3.1什么是大小端

超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,将其分为 ⼤端字节序存储和⼩端字节序存储 .
  • 大端存储模式:是指数据的低位字节内容保存在内存的高地址处,而数据的高位字节内容,保存在内存的低地址处.
  • 小端存储模式:是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的低地址处.

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

  1. 在计算机系统中,是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit.但是在C语言中除了8bit的char类型数据外,还有16bit的short,32bit的int,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题.因此就导致了大端存储模式与小端存储模式.
  2. 一个 16bit short x , 在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11
    高字节, 0x22 为低字节。对于 大端模式,就将 0x11 放在低地址 中,即 0x0010 中, 0x22 放在高地址 中,即 0x0011 中。 小端模式,刚好相反 。我们常用的 X86 结构是小端模式 ,而 KEIL C51 则为大端模式。很多的ARM DSP 都为小端模式。有些 ARM 处理器还可以由硬件来选择是大端模式还是小端模式。

3.3:判断一个机器的字节序

当了解了大小端的概念后,那么我们该如何判断一个机器是大端存储方式还是小端存储方式.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int check_sys()
{
	int value = 1;
	return (*(char*)&value & 1);
}
int main()
{
	int result = check_sys();
	if(result == 0)
	{
		printf("大端存储\n");
	}
	else
	{
		printf("小端存储\n");
	}

}

4:相关练习

了解了整数在内存中的存储后,接下来我们来看几个小练习

4.1:练习1

#define _CRT_SECURE_NO_WARNINGS
#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;
}

4.2:练习2

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

4.3:练习3

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

4.4:练习4

#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;
}

4.5:练习5

#include <stdio.h>
unsigned char i = 0;
int main()
{
     for(i = 0;i<=255;i++)
     {
       printf("hello world\n");
     }
     return 0;
}

上述代码呢,很明显,会发生死循环,那么是为什么呢?

4.6:练习6

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

通过观察上面的结果,我们可以看到,此代码也会发生死循环,那么是为什么呢,有的uu会很奇怪,当i ==0时,此时再对其进行--,不应该是到 -1了吗,那么这是为啥呢?

5:浮点数在内存中的存储

常见的浮点数:3.14159、1E10等,浮点数家族包括:floatdoublelong double 类型.那么浮点数在内存中是如何存储的呢,我们首先来看下面这段代码.

5.1:代码1

#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("num的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);
	return 0;
}

num和*pFloat明明在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大,这是为什么呢?这里就涉及到了浮点数在内存中的存储方式了.

5.2:浮点数存储规则

根据国际标准IEE754标准,任何一个二进制浮点数V可以表示成下面的形式.

  • V = (-1)^S * M * 2 ^ E
  • (-1)^S表示符号位,当S为0时,V为整数,当S为1时,V为负数.
  • M表示有效数字,M是大于等于1,小于2的.
  • 2^E表示指数位.

了解了浮点数的存储规则后,我们来看几个例子.

5.2.1:例子1

5.2.2:例子2

5.2.3:IEE754规定

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

对于 64位 的浮点数, 最⾼的1位存储符号位S ,接着的 11位存储指数E ,剩下的 52位存储有效数字M .

5.3:浮点数存储的过程

IEE754对有效数字M和指数M,有一些特别的规定.

在上面有提到过,1 <= M < 2,那么也就是说,M可以写成1.xxxxxxx的形式,其中xxxxxxx表示小数部分.

IEE754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分.比如保存1.01的时候,只保存01,等到要对其进行读取的时候,再把第一位的加上去.这样子做的目的是为了节省一位有效数字.以64位浮点数为例,留给M的只有52位,将第一位的1加进来以后,等于可以保存53位有效数字.

至于指数E,情况就有些复杂.

首先,E为一个无符号整数(unsigned int)

这就意味着,若E为8位,那么它的取值范围为0~255;如果E为11位,它的取值范围为0~2047.但是,我们知道,科学计数法中的E是可以出现负数的,所以IEE754规定,存入内存E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023.例如,2^10的E是10,所以保存为32位浮点数时,必须保存成10 + 127 = 137.即10001001.

5.4:浮点数取的过程

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

5.4.1:E不全为0或不全为1

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

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

0 01111110 00000000000000000000000.

5.4.2:E为全0

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

5.4.3:E为全1

这个时候,如果有效数字M全为0,表示±无穷大.

6:题目解析

了解了浮点数的存储过程后,我们再回到最初的代码

#include <stdio.h>
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);
    9
    /*
    补码:000000000000000000000000000001001

    从浮点数存储的角度出发
    
    0(S) 00000000(E) 000000000000000000001001(M)
    
    由于E为全0,那么此时E的真实值是1 - 127(32位) = -126

    M = 0.000000000000000000001001

    S = 0 

    因此V = (-1)^0 * 0.000000000000000000001001 * 2^-126;*/
	printf("*pFloat的值为:%f\n", *pFloat);//无限接近于0
	*pFloat = 9.0;
    /*二进制序列:1001.0

    V = (-1)^0 * 1.001 * 2^3;

    S = 0, M = 1.001 , E = 3;

    将其还原为二进制序列时,

    首先E + 127(32位) = 130------->10000010
    M去掉整数位后为001,然后后面补0,
    因此二进制序列是

    0(S) 10000010 (3 + 127---->E)001 0000 0000 0000 0000 0000*/
	printf("num的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);
	return 0;
}

9

补码:000000000000000000000000000001001

从浮点数存储的角度出发

0(S) 00000000(E) 000000000000000000001001(M)

由于E为全0,那么此时E的真实值是1 - 127(32位) = -126

M = 0.000000000000000000001001

S = 0 

因此V = (-1)^0 * 0.000000000000000000001001 * 2^-126-------->无线接近于0

 

9.0

二进制序列:1001.0

V = (-1)^0 * 1.001 * 2^3;

S = 0, M = 1.001 , E = 3;

将其还原为二进制序列时,

  • 首先E + 127(32位) = 130------->10000010
  • M去掉整数位后为001,然后后面补0,

因此二进制序列是

0(S) 10000010 (3 + 127---->E)001 0000 0000 0000 0000 0000

好啦,uu们,数据类型在内存中的存储这部分滴详细内容博主就讲到这里啦,如果uu们觉得博主讲的不错的话,请动动你们滴小手给博主点点赞,你们滴鼓励将成为博主源源不断滴动力,同时也欢迎大家来指正博主滴错误~

评论 36
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值