C语言——数据在内存中的存储

char c1 = -1;
signed char c2 = -1;
unsigned char c3 = -1;

printf("c1=%d\n", c1);
printf("c2=%d\n", c2);
printf("c3=%d\n", c3);

printf("-------------------------\n");

char c4 = -128;
char c5 = 128;

printf("c4=%u\n", c4);
printf("c5=%u\n", c5);

printf("-------------------------\n");

当你看到上面的代码时,你的第一反应觉得打印结果分别会是什么?

不知道上述答案跟你所认为的是否相同,但是想要知道题目的答案为何是如此,就需要了解不同的数据类型在内存中是如何存储的。

目录

一、数据类型简介

二、整型在内存中的存储

三、浮点型在内存中的存储

1、举例说明

2、存储规则


一、数据类型简介

数据类型大致可以分为两大家族:整型家族和浮点型家族。

整型家族中包括:char、int、short、long、long long。

其中本为字符型的 char 之所以也归为整型其中的一种,那是因为字符在存储时是存储其 ASCII 值,ASCII 值是整数,所以 char 也为整型。

每种整型又可以根据是否为带符号整型,可以添加前缀细分为:有符号整型 signed ~~ 和 无符号整型unsigned ~~ 两种。其中,除了 char 之外的整型,在没有加前缀的情况下,都默认视作有符号型,即等同于 signed~~。而 char 则不一定,要根据不同编译器下分不同情况,但在常见的编译器下,char 也被默认为 signed char。

浮点型家族包括:float 和 double。

数据分为不同的类型的意义有两点:

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

2)如何看待内存空间的视角。(从不同视角看待数据在内存的存储,其意义不同) 

除了上述两种主要类型,还有构造类型、指针类型和空类型。

构造类型包括:数组类型、结构体类型、枚举类型、联合类型。其中数组的类型是根据数组元素的类型和元素个数进行区分的。例如:int arr[10] 的类型就是 int [10],int arr[11] 的类型是 int [11],char ch[5] 的类型是 char [5]。

指针类型则包含指向各种基本数据类型的指针类型,例如:int *、float * 、void * 等。void * 表示无具体类型指向的一种指针类型。

void 就表示空类型,通常应用于函数的返回类型、函数的参数、指针类型。

二、整型在内存中的存储

计算机中的整数有三种表示二进制的方法:原码、反码和补码。

这三种表示方法通俗点理解就是,原码就是我们直接看到的数据的二进制形式,而补码是数据在内存中的存储形式,反码是作为其中的一种过渡形式。

以上的三种表示方法均有符号位和数值为两部分:符号位都是用 0 表示正,1 表示负。而数值位在正负数情况下的表示不相同。

正数的原、反、补三种形式都相同,都是直接将数值转换为二进制形式而已。

负数的三种形式则各不相同:

原码跟正数相同,也是直接将数值转换为二进制形式即可。

反码则是在原码的基础上,符号位不变,数值位按位取反。、

补码则是在反码的基础上 +1 。

上面说到补码是整数在内存中的存储方式,这是由于使用补码可以避免使用原码计算时的不便之处。而且,我们知道,数据在内存中存在方式是一种形式,但是我们看到的面板数据是另一种形式,而当我们需要将数据从内存中取出,用于我们进行观察时,例如当我们打印出来时,需要将内存中存在的形式转换为面板数据的形式。所以采用补码的另一个好处是,将他与原码进行相互转换时,其运算过程是相同的。原码转换为补码,是先按位取反后 +1 ,而将补码转换为原码,可以进行反操作进行还原,但是也可以同样按位取反之后 +1 ,这样也是可以得到原码的,所以是同样的计算过程。在这样的情况下,计算机就不需要额外的硬件电路。所以计算机采用补码作为整型数据在内存中的存储方式。

三、浮点型在内存中的存储

1、举例说明

首先要明确的是,浮点型的存储形式与整型完全不同,所以没有原、反、补的概念。

这一点我们通过下面一个例子就可以感知到:

int n = 9;
float* p = (float*)&n;
printf("n=%d\n", n);
printf("*p=%f\n", *p);

在这个例子中,我们通过一个指针变量 p 取得了 int 型变量 n 的地址,并且强制转换为浮点型指针。假设整型和浮点型的存储方式相同,那么第一次打印中,n 和 *p 的值应该是相同的。

很明显,事实并非如此,所以已经可以确定整型和浮点型在内存中的存储方式并不相同。

2、存储规则

根据国际标准 IEEE(电气与电子工程协会)754,任意一个二进制浮点数 V 可以表示成以下形式:(-1)^ S * M * 2 ^ E 。

(-1)^ S 表示符号位,当 S = 0,V 为正数;当 S = 1,V 为负数;

M 表示有效数字,大于等于 1 ,小于 2 ;

2 ^ E 表示指数位。

举个例子,比如十进制的 5.0,其二进制写法就是 101.0,相当于 1.01 * 2 ^ 2。

按照上述,就可以得出 S = 0,M = 1.01,E = 2 。

既然每个浮点数都可以转换成这样的格式,那么只要将 S、M、E 的值分别存储起来,当要使用该数据时,就将这三个数值取出并转换成相对应的浮点数即可。

IEEE 754 规定:对于 32 位的浮点数,也就是 float ,最高的 1 位是符号位 S,接着的 8 位是指数 E,剩下的 23 位是有效数字 M;对于 64 位的浮点数,也就是 double ,最高 1 位仍然是符号位 S,接下去的 11 位是指数 E,剩下的 52 位是有效数字 M。

对于这三个数值具体如何存储,IEEE 754 还有一些特别规定:

对于 S,只需要将其值直接放入首位即可,不用进行其余操作;

对于 M,由于 1≤M<2,也就是说,M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。那么,计算机只需要保存小数部分,而固定的 1 不用保存,只需要在取出的时候自动加上就可以了,这样就可以节省出 1 位有效数字来存储数据。

对于 E,其存储和取出都有特殊的规则:

存储时,由于 E 是一个无符号整数,这就导致了在存储负数时无法直接存储,所以规定在存储时,E 的值要加上一个中间数,对于 8 位的 E,中间数是 127;对于 11 位的 E,中间数是 1023。这样就可以确保 E 可以存储所有负数。例如:2 ^ (-2),在存储 E 时,将 E 的值 -2 加上 127 或者 1023 再进行存储。

取出时,E 分为三种情况进行取出:

当 E 不全为 0 或 1 时,此时就将 E 的值减去 127 或 1023 即可;

当 E 全为 0 时,此时浮点数的指数 E 等于 1-127 或 1-1023,此时的浮点数是一个非常小的数值,所以此时的 M 在取出的时候不再加上 1,直接取出,即为 0.xxxxxx,这样做是为了表示 ±0 或者表示此时的浮点数是一个接近于 0 的数字;

当 E 全为 1 时,此时直接表示为 ±无穷大即可,因为此时的浮点数指数已经非常大了,浮点数接近于无穷大。

浮点数的存储方式,导致了其有可能无法精确存储。例如:3.14,当你用上述方法计算时,你会发现根本没办法用小数点后的二进制精准表示。

 我们也可以看到,当我们存储 3.14 的时候,编译器也是无法精准存储,只能做到近似。

这是由于我们用于存储有效数字 M 的位数有限,当某一个数值的二进制表示方法的有效数值超出 M 的位数时,或者根本没办法用二进制精准表示时,计算机就只能做到近似存储。由于 double 留给 M 的位数比 float 更多,所以 double 的精度比 float 更高,但是也只是相对程度上更加精确,仍旧无法保证完全精确。

以上就是作者本人对于数据存储的理解,如果有错误之处希望能够指出,我得以加以改进。浮点型数据存储这方面知识虽说很少会直接作为试题考核,但是了解之后有助于我们对于数据类型的理解。

  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值