Bootstrap

C语言:数据在内存中的存储(详解)

本文详细解释了整型和浮点型数据在内存中的存入以及取出规则,大小端介绍,超详细易懂,包含题目加深理解

一、数据类型的基本归类

(1)整型
① char - unsigned/signed char (字符数据类型)
字符存储的时候存储的是ASCII码值,是整型,所以归类的时候放在整型家族
char型数据占1个字节,取值范围是-128~127

② short - unsigned/signed short (短整型)
short型占2个字节,取值范围是-32768~32767

③ int - unsigned/signed int (整型)
int型数据占4个字节,取值范围是-2147483646~2147483647

④ long - unsigned/signed long (长整型),占4/8个字节
C语言只规定sizeof(long)>=sizeof(int),并没有具体规定long的大小,所以在不同的编译器中long的大小可能不一样

⑤ long long (更长的整型),占8个字节

对于整型家族来说有:有符号和无符号的区分(浮点数没有这个说法)
short = signed short
int = signed int

char到底是signed char 还是 unsigned char 不确定,不同编译器可能不一样。在VS编译器上 char 是signed char。

(2)浮点数
float 、double 、long double

(3)构造类型
构造类型即自定义类型
①数组类型
例:int arr[10] 类型是 int [10]
char arr[5] 类型是 char [5]
数组类型由数组个数和元素类型决定

②结构体类型 struct
③枚举类型 enum
③联合类型 union

(4)指针类型
int *pi、char *pc、float *pf、void *pv
还包括结构体指针

(5)空类型
void表示空类型(无类型),通常用于函数的返回类型,函数的参数,指针类型

二、整型在内存中的存储

对于语句 int a = 20 ; 我们知道是创建一个变量a,a向内存申请4个字节来存放数据,那么这四个字节是如何来存储20的呢?要搞清楚这个问题,我们需要先弄清楚什么是原码,反码,补码。

(1)原码、反码、补码
它们是计算机中的三种2进制表示整数方法(注意是表示整数的),它们均有符号位,数值位。
符号位:0表示正,1表示负
数值位:根据数据本身计算得出

对于正数:原,反,补码都相同
对于负数:三种表示方法各不相同
原码:直接将数值按照正负数的形式翻译成二进制
---------例如 -20 的原码就是10000000 00000000 00000000 00010100
因为用4个字节存储,1字节是8bit,也就是32bit位存储-20,不足位数在前面补0,最高位用1/0表示正负符号
反码:将原码符号位不变,其他位依次按位取反
---------例如 -20 的反码就是11111111 11111111 11111111 11101011
补码:反码+1得到补码
---------例如 -20 的补码就是11111111 11111111 1111111 11101100

对于整型来说,内存中存放的是数据的补码

在VS编译器中,为了方便展示,显示的是16进制,实际上内存中存放的是2进制,4个2进制转换一个16进制位,所以-20在vs上面存储需要8个16进制,把每四个二进制依次换算成16进制可以得到
ff ff ff ec,我们打开VS验证一下
在这里插入图片描述
可以看到确实存储的是变量a的补码,可是为什么顺序不是ff ff ff ec,而是颠倒的呢?这涉及到下一个内容,大小端存储。

(2)大小端存储
在计算机系统中,内存是以字节为单位的,对于超过一个字节的数据,必然存在如何将多个字节安排在内存中的问题,所以产生了大小端字节序存储模式,可想而知对于char型数据只占一个字节,没有存储顺序之分。
大端字节序存储:是指数据的低位保存在内存中的高地址,而数据的高位,保存在内存中的低地址
小端字节序存储:是指数据的低位保存在内存中的低地址中,而数据的高位。保存在内存的高地址
例如有十六进制数0x 11223344

在这里插入图片描述

题目1:写一个程序来判断当前机器是大端存储还是小端存储

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

题目2:下列程序输出什么?

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

在这里插入图片描述
在这里插入图片描述

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

浮点数即内存中的小数
浮点数家族包括:float(单精度),double(双精度),long double(更长的双精度)

(1)浮点数存储规则
根据标准,任意一个二进制浮点数V可以表示成下面的形式:(-1)S * M * 2E
(-1)S表示符号位,当S=0,V为正数;当S=1,V为负数
M表示有效数字,M大于等于1,小于2
2E表示指数位

例如:V=5.5(十进制浮点数)=101.1(二进制浮点数)=1.011*22=(-1)0 * 1.011 *22
S=0 M=1.011 E=2
在这里插入图片描述
规定:对于32位浮点数(即float型),最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M
在这里插入图片描述
对于64位浮点数(即double型),最高1位是符号位S,接着的11位是指数S,剩下的52位为有效数字M
在这里插入图片描述
(2)对有效数字M和指数E,还有一些特别规定:
①有效数字M:
前面说过,1<M<2,也就是说,M可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分。
规定在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字,则可以多保存一位数字。

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

例如:上面提到的5.5如何在内存中存储?
V=(-1)0 * 1.011 *22
S=0 M=1.011 E=2
E在内存中存储的数为2+127=129
在这里插入图片描述
上面提到的是存入内存,指数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)

例题:下列程序输出结果是什么

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

在这里插入图片描述
在这里插入图片描述

;