【C语言】数据在内存中的存储(原反补码、大小端、浮点型的存储解析)

前言

本篇主要内容:

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

一、数据类型介绍

前面介绍过C语言中的基本数据类型

类型名称                 字节大小(byte)//32位机器下所占空间
char(字符型)                 1
short(短整型)              2    
int(整型)                  4    
long(长整型)               4   
long long(长整型)          8  
float(单精度浮点型)         4  
double(双精度浮点型)        8 

接下来我们把类型归一下类

整型家族:

char//字符的本质是ASCII值,所以归为整型
	unsigned char
	signed char
short
	unsigned short
	signed short
int
	unsigned int
	signed int
long
	unsigned long
	signed long

浮点型家族:

float
double

构造类型:

数组类型		//比如int arr[5]的类型是 int[5]
结构体类型 	struct
枚举类型 	enum
联合类型 	union

指针类型:

int* pa;
char* pc;
float* pf;
void* pv;

二、整型在内存中的存储

一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。

比如:

int a = 10;
char b = 'A';

我们给a分配了4个字节的空间,给b分配了1个字节的空间,而数据在内存中又是怎样存储的呢?

1.原反补码

计算机中的整数有3种二进制表示方法,即原码、反码和补码。其中第一位是符号位,0表示正数,1表示负数。

整数在内存中是以二进制的补码形式存储的

这是因为,使用补码可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

正数的原、反、补码都相同。负整数的三种表示方法需要进行转换。

原码:直接将一个数翻译成二进制(负数的符号位是1)
反码:原码的符号位不变,其他位按位取反
补码:反码+1

注:负数从原码到补码和从补码到原码都是:符号位不变,其他位按位取反,最后再+1

在这里插入图片描述

2.大小端

在vs中我们可以看到变量在内存中的存储
在这里插入图片描述

我知道a的补码十六进制表示为0x0a(也即0x00 00 00 0a),b的补码十六进制表示为0xff ff ff f6,但上述存储结果与我们预想的不一样,似乎顺序反过来了,这就牵扯到大小端问题了。

大小端是一种存储模式:

大端(存储)模式:是指数据的位保存在内存的地址中,而数据的位保存在内存的地址中;
小端(存储)模式:是指数据的位保存在内存的地址中,而数据的位保存在内存的地址中。

再看上图,0a是a的低位,保存在内存中的低地址,所以是小端模式。

我们常用的 x86 结构是小端模式,而 Keil C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

除了查看内存地址这种方式,我们是否能通过代码来测试当前机器的字节序是小端还是大端呢?

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

i 在内存中存储为0x00 00 00 01,i 的地址是int*类型 ,然后强制转换成char*类型发生截断,取到了i 在内存中存储的低地址,再解引用拿到低地址中的内容。
如果是大端存储,低地址放的是 i 数据中的高位也就是00;
如果是小端存储,低地址放的是 i 数据中的低位也就是01。

这里涉及到了强制转换、整型提升和截断,具体详情可以移步我的另一篇笔记操作符详解的末尾位置。

3.整型数据的范围

整型家族的取值范围在limits.h中定义

类型名称范围
char-128~127 (-2^7 ~ 2^7-1)
unsigned char0~255 (0 ~ 2^8-1)
short-32768~32767 (-2^15 ~ 2^15-1)
unsigned short0~65534 (0 ~ 2^16-1)
int-2147483648~2147483647 (-2^31 ~ 2^31-1)
unsigned int0~4294967295 (0 ~ 2^32-1)

unsigned表示无符号类型,不存在符号位,所以没有负数

练习一:

#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 -1 255

整型提升:有符号补符号位,无符号则补 0。

在这里插入图片描述练习二:

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

在这里插入图片描述

注意:%u是以无符号十进制形式打印,%d是以有符号十进制形式打印

-128的补码:11111111 11111111 11111111 10000000
a的补码:10000000 (截断)
整型提升后:11111111 11111111 11111111 10000000
无符号形式打印,直接翻译成十进制,是一个很大的数

练习三:

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

在这里插入图片描述
下面我画一张图,就很容易理解了。

比如拿上面这段代码-128 -1,则-128逆时针走一格到127。

short和int类型同理。

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

常见的浮点数类型有float和double,浮点型家族的范围在float.h中定义

float f = 3.14;
double x = 1E5;//1.0*10^5

整型和浮点型在内存中的存储方式是不一样的

浮点数的存储规则

根据国际标准IEEE(电气和电子工程协会) 754规定,任意一个二进制浮点数可以表示成下面的形式:

(-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 。通过 (-1)^S * M * 2^E 可以得出S=0,M=1.01,E=2。
十进制的-5.0 = -101.0 = -1.01×2^2 。所以,S=1,M=1.01,E=2

对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M
在这里插入图片描述

对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M

在这里插入图片描述

IEEE 754对有效数字M和指数E,还有一些特别规定:

M: 因为1<=M<2,M=1.xxxx的第一位默认总是1,所以这个1可以省略,读取的时候再加上去,这样M可以多存一位有效数字,精度更高。
E: E是一个无符号整数 unsigned int类型,32位浮点数中E只有8位,取值范围为0 ~ 255;64位浮点数中E只有11位,取值范围为0~2047;但是,科学计数法中指数E是可以有负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。

比如:
存一个32位浮点数1E5,E=5+127=132 即 1 0000 0100;
存一个64位浮点数1E10,E=10+1023=1033 即 100 0000 1001


指数E从内存中取出又分三种情况:

E不全为0或不全为1: 在取出时E要减去中间值127(或1023)即为真实值,再将有效数字M前面加上第一位的1。
E全为0: 浮点数的指数E等于1-127(或1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
E全为1: 这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位S)

比如说 float f = 5.5f;

5.5 = 101.1=(-1)^0 * 1.011 * 2^2 --> S = 0; M = 1.011; E = 2;
存入内存的数据就是:S = 0; M = 011后面补20个0; E = 2+127 = 129(10000001)
即 0 10000001 01100000000000000000000
从内存中还原出来: S = 0; M = 1.011(前面记得补1); E= 129-127=2 即 (-1)^0 * 1.01100000000000000000000 * 2^2

来看一道示例:

在这里插入图片描述

分析:
在这里插入图片描述

最后:
数据的存储笔记暂时总结到这里啦,建议与另一篇笔记结合食用哦C语言操作符详解

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值