进阶C语言之数据在内存中的存储

进阶C语言之数据在内存中的存储,第1张

目录

数据类型介绍

类型的基本归类

整型家族

浮点数家族

构造类型

指针类型

空类型

整型在内存中的存储

二进制

原码、反码、补码

大小端字节序

练习

浮点型在内存中的存储

一个例子

浮点数的存储规则

存储

读取

数据类型介绍

C语言的基本内置类型

名称解释占据空间(单位字节)
char字符1
short短整型2
int整型4
long长整型4(32位)/8(64位)
long long长长整型8
float浮点型4
double双精度浮点型8

类型的意义:

一、决定了开辟的内存空间的大小。

二、决定了看待内存空间的角度(存入和读取数据的方式)。 

类型的基本归类 整型家族
名称解释备注
char字符标准为定义有符号或则无符号,具体看编译器
signed char有符号
unsigned char无符号
short短整型默认有符号
signed short有符号
unsigned short无符号
int 整型默认有符号
signed int有符号
unsigned int无符号
long长整型默认有符号
signed long有符号
unsigned long无符号

long long长长整型默认有符号
signed long long有符号
unsigned long long无符号

 signed与unsigned的区别:

数据在内存中是二进制储存。

浮点数家族
名称解释备注
float单精度浮点型精度低,表示范围小
double双精度浮点型精度高,表示范围大
构造类型
名称解释备注
type [const_n]数组类型
struct tag结构体类型
enum tag枚举类型
union tag联合体类型
指针类型
名称解释备注
int *整型指针
char *字符指针
float *浮点指针
void *空指针
.................
空类型

void类型表示空类型,通常用于函数返回值,函数参数,空指针。

整型在内存中的存储 二进制

数据在内存中以二进制的形式存储,编译器下表现为十六进制。

原码、反码、补码

正数的原码,反码,补码相同,负数的原码,反码,补码需要计算。

对整型来说,内存中存放的就是补码。

但是为什么使用补码呢?

原因有三:

一、使用补码,可以将符号位和数值位统一处理。

二、加法和减法可以同一处理(CPU只有加法器)。

三、补码与原码可以相互转换,同样使用取反加一的方式,不需要额外的硬件电路。

 10的原码:0000 0000 0000 0000 0000 0000 0000 1010
 10的反码:0000 0000 0000 0000 0000 0000 0000 1010
 10的补码:0000 0000 0000 0000 0000 0000 0000 1010

-20的原码:1000 0000 0000 0000 0000 0000 0001 0100
-20的反码:1111 1111 1111 1111 1111 1111 1110 1011
-20的补码:1111 1111 1111 1111 1111 1111 1110 1100

补码相加:0000 0000 0000 0000 0000 0000 0000 1010
补码相加:1111 1111 1111 1111 1111 1111 1110 1100
相加结果:1111 1111 1111 1111 1111 1111 1111 0110(补码)
上面的运算符号位与数值域同一处理,同时10-20的运算可以看作10+(-20)直接使用加法。

解析成结果

补码:1111 1111 1111 1111 1111 1111 1111 0110
反码;1000 0000 0000 0000 0000 0000 0000 1001(除符号位取反)
原码:1000 0000 0000 0000 0000 0000 0000 1010(反码+1)

大小端字节序

内存开辟空间后,存入的数据该怎么储存呢?

实际上有两种储存的方法。

为什么会有大小端字节序呢?

这是因为计算机系统中,是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8比特,但是也有16比特和32比特,对于大于8比特的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题,因此就有了大小端字节序。

常用的X86结构式小端模式,而KEIL C51则为大端模式。

练习

设计一个小程序来判断当前机器的字节序。

int check_sys()
{
	int a = 1;
	return *(char *)&a;
}

int main()
{
	if (check_sys())
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	system("pause");
	return 0;
}

 以下代码输出什么

int main()
{
	char a = -1;
	signed char b = -1;
	unsigned char c = -1;
	printf("%d %d %d\n", a, b, c);
	system("pause");
	return 0;
}
过程

char标准未定义,VS下char默认为signed char

所以a,bc中的补码为;1111 1111

打印时,按照类型进行整型提升,所以a,b提升后的补码为:1111 1111 1111 1111 1111 1111 1111 1111

c是unsigned提升后为:0000 0000  0000 0000 0000 0000 1111 1111.

%d打印是有符号打印,所以a,b的结果为-1,c的结果为255。

以下代码运行什么 

int main()
{
	char a = -128;
	printf("%u\n", a);
	system("pause");
	return 0;
}
-128原码:
1000 0000 0000 0000 0000 0000 1000 0000
-128反码:
1111 1111 1111 1111 1111 1111 0111 1111
-128补码:
1111 1111 1111 1111 1111 1111 1000 0000
截断存入a:
1000 0000
无符号类型打印
整型提升(char类型,俺符号位提升)补码:
1111 1111 1111 1111 1111 1111 1000 0000
打印

 以下代码运行什么 

int main()
{
	char a = 128;
	printf("%u\n", a);
	system("pause");
	return 0;
}

128原码:
0000 0000 0000 0000 0000 0000 1000 0000
128反码:
0000 0000 0000 0000 0000 0000 1000 0000
128补码:
0000 0000 0000 0000 0000 0000 1000 0000
截断存入a:
1000 0000
无符号类型打印
整型提升(char类型,俺符号位提升)补码:
1111 1111 1111 1111 1111 1111 1000 0000
打印

 以下代码运行什么 

int main()
{
	int i = -20;
	unsigned j = 10;
	printf("%d\n", i + j);
	system("pause");
	return 0;
}

代码执行过程设计到类型转换
int类型转换成unsigned int类型
-20补码:
1111 1111 1111 1111 1111 1111 1110 1100

20的补码:
0000 0000 0000 0000 0000 0000 0000 1010

相加(注意相加后的数据是无符号类型)
1111 1111 1111 1111 1111 1111 1111 0110(补码)
但是打印是有符号打印所以是-10

 以下代码运行什么 

int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
	system("pause");
	return 0;
}
for的表达式中,i是无符号类型,所以一直大于等于0,所以结果为死循环。
循环的内容为从9到0,再到i的类型不断减小的循环。

  以下代码运行什么 

int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	system("pause");
	return 0;
}

char类型的取值范围是从-128-127。a[i]的值从-1,-2,-3.........0,之后继续循环。
strlen函数求的是数组中0元素之前的数,a[1000]中,0之前的数是255个。
浮点型在内存中的存储

浮点数就是小数,之所以叫浮点数是因为小数点可以移动。

浮点数家族包括:float,double,long double(C99定义)。

浮点数的具体取值范围在float.h中。

整数的具体取值返回在limits.h中。

那么浮点数与整数除了小数点的区别外,还有什么不同呢?

一个例子
int main()
{
	int i = 9;
	float *pi = (float *)&i;
	printf("1=%d\n", i);
	printf("2=%f\n", i);
	*pi = 9.0f;
	printf("3=%d\n", i);
	printf("4=%f\n", i);
	system("pause");
	return 0;
}

 上面的例子中

1=9,2=0.000000,内存中存储的明明是整型9,为什么浮点打印时会变成0.000000而不是9.000000呢?

解引用*pi并赋值9.0后,

3=1091567616,4=9.000000,内存中明明存储的是9.0,为什么整型打印会打印出1091567616呢?

原因是浮点数的存储规则与整数不同。

浮点数的存储规则

SME存储规则。

IEEE754中规定,浮点数以SME的方式存储,所谓的SME是指任何一个浮点数V都可以表示成下面的形式:

V=(-1)^S * M *2^E。

其中(-1)^S表示符号位,当S=0是,V为正数,当S=1时,V为负数。

M表示有效数字,取值范围大于等于1,小于2。

2^E表示指数位,2是因为使用的是二进制。

举个例子:

浮点数

5.0
二进制101.0
公式(-1)^0*1.01*2^2
S0
M1.01
E2

浮点数

9.5
二进制1001.1
公式(-1)^0*1.0011*2^3
S0
M1.0011
E3

为什么9.5的二进制写法是1001.1而不是1001.101呢?

原因是二进制的权重

1001.10
3210-1-2

 所以这也是为什么有些浮点数可能只有近似数却没有准确值的原因。

大概了解了S,M,E那么浮点数在内存中是怎么存储的呢?

存储

浮点数在内存中的存储格式如下:

32位机器下:

S1bitE8bitM23bit

64位机器下:

S1bitE11bitM52bit

有效位M在存取时会省略1.xxxxxxxx前的1,只保存 xxxxxxxx部分,这样可以节省1bit,保存有效位24位。

指数位E情况比较复杂,首先指数E是无符号整数,就是说保存在E中的数据首位是数据位,但是实际的浮点数指数有正有负,所以必须选择另一种方式,表达正负的形式,那就是中间数。

中间数:就是取指数位数据范围的中间值,并在保存指数位的时候加上中间值,这样保证存入指数位的始终都是正数。

指数位数指数范围中间值
32位机器下80-255127
64位机器下110-20471023

举例:

当指数为5时,E=5+127=132,二进制写法1000 0100。

当指数为-1时,E=-1+127=126,二进制写法0111 1110。

读取

读取分为三种情况:

一、E不全为0或者全为1是,E减去中间值,M前加上1,之后进行计算

二、E全为0时,E等于1-中间值,,M不再加上1,数字无限接近于0

三、E全为1时,如果有效数字M全为0,表示正负无穷大。

解释例题:

int main()
{
	int i = 9;
	float *pi = (float *)&i;
	printf("1=%d\n", i);
	printf("2=%f\n", i);
	*pi = 9.0f;
	printf("3=%d\n", i);
	printf("4=%f\n", i);
	system("pause");
	return 0;
}
int i = 9;
内存中
0000 0000 0000 0000 0000 0000 1000 0001

使用int类型读取,结果为9
使用float类型读取,结果为0,因为E全为0.

*pi = 9.0f;
1001.0->1.001-> S = 0, M = 001,E = 1000 0010
内存中
0 10000010 00100000000000000000000
0100 0001 0001 0000 0000 0000 0000 0000

使用int类型读取,结果为1091567616
使用float类型读取,结果为9.000000.

注意:超出1个字节的数据类型都必须遵守大小端字节序。 

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/3002811.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-27
下一篇 2022-09-27

发表评论

登录后才能评论

评论列表(0条)

保存