『C』数据的存储

数据类型介绍

前面我们已经学习了基本的内置类型以及他们所占存储空间的大小。

类型的意义

使用这个类型开辟内存空间的大小。
如何看待内存空间的视角。

类型的归类

整型家族

charunsigned char
signed char
shortunsigned short
signed short
intunsigned int
signed int
longunsigned long
signed long

浮点型家族

float
double

构造类型

数组类型		arr
结构体类型	struct
枚举类型		enum
联合类型		union 

指针类型

char*
short*
int*
long*
float*
double*

空类型

  • 常用在程序别写中对定义函数的参数类型、返回值、函数中指针类型进行声明。void的字面意思是“无类型”,void则为“无类型指针”,void可以指向任何类型的数据。
  • C语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是很多程序猿却误以为是void类型。
    在这里插入图片描述

整型在内存中的存储

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

编译器数据模型

编程语言上的32位与64位差异主要体现在基本类型的位长上。C/C++等语言仅仅定义了这些基本类型数据之间的关系并没有严格定义它们的字长。不同操作系统平台上,根据编译器不同的实现,它们的字长如下表所示:

数据类型LP64ILP64LLP64ILP32LP32
char88888
short1616161616
int3264323216
long6464323232
long longN/AN/A64N/AN/A
pointer6464643232
  • 其中LP64、ILP64和LLP64是64位平台上的字长模型ILP32和LP32是32位平台上的字长模型
  • LP64指long和pointer是64位ILP64指int、long和pointer是64位LLP64指long long和poniter是64位
  • ILP32指int、long和pointer是32位LP32指long和pointer是32位的
  • 64位windows一般采用的是LLP64数据模型
  • 64位Unix,Linux一般采用LP64模型

数组在所开辟内存中到底是如何存储的呢?

比如

int a = 20;
int b = -10;

我们知道a共占有四个字节的空间。那如何存储?

原码、反码、补码

计算机中的符号数有三种表示方法,即原码反码补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示正,用1表示负,而数值位三种表示方法各不相同

  • 原码:直接将二进制按照正负数的形式翻译成二进制就可以了。
  • 反码:将原码的符号位不变,其他位一次按位取反就可以看到了。
  • 补码:反码+1就得到补码。整数的原、反、补码都相同

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

为什么?

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路
我们来看看数据在内存中的存储:
在这里插入图片描述
我们可以看到对于a和b分别存储的是补码,但是我们发现顺序有点不对劲,这是为什么呢?

大小端介绍

什么是大端小端?

  • 大端存储模式:指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中。
  • 小端存储模式:指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地址中。
    在这里插入图片描述

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

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

在乔纳森·斯威夫特的著名讽刺小说《格列夫游记》中,小人国内部分裂成Big-endian和Little-endian两派,区别在于一派要求从鸡蛋的大头将鸡蛋打破,另一派要求从鸡蛋的小头把鸡蛋打破。斯威夫特借以讽刺英国的政党之争,在计算机工业中值数据储存顺序的分歧。

大小端判断

代码一
#include <stdio.h>

int isLittleEnd(){

	int i = 1;

	return *((char*)&i);
}

int main(){

	if(isLittleEnd()){
		printf("Little End!\n");
	}
	else{
		printf("Big End!\n");
	}

	return 0;
}
运行结果
[sss@aliyun data]$ !gcc
gcc little_end.c -o little_end
[sss@aliyun data]$ ./little_end 
Little End!
代码二
#include <stdio.h>

int isLittleEnd(){

	union{
		int i;
		char c;
	} un;

	un.i = 1;

	return un.c;
}

int main(){

	if(isLittleEnd()){
		printf("Little End!\n");
	}
	else{
		printf("Big End!\n");
	}

	return 0;
}
运行结果
[sss@aliyun data]$ !gcc
gcc little_end.c -o little_end
[sss@aliyun data]$ ./little_end 
Little End!

浮点型在内存中的存储

常见的浮点数:

3.1415926	1E10

浮点数家族包括:

float	double	long double

浮点数表示范围
float.h中定义

代码演示

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

运行结果

[sss@aliyun data]$ gcc float.c -o float
[sss@aliyun data]$ ./float 
n: 9
*pfloat: 0.000000
n: 1091567616
*pfloat: 9.000000

num和*pfloat在内存中明明是同一个数字,为什么浮点数和整数的解读结果会差别那么大?要理解这个结果,一定要搞懂浮点数在计算机内部的表示方法。
根据国际标准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。那么,按照上面V的格式,可以得出S = 0,M = 1.01,E = 2。
  • 十进制的-5.0,写成二进制是-101.0,相当于-1.01 * 2^2。那么,S = 1,M = 1.01,E = 2。

IEEE 754标准

对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。
在这里插入图片描述
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
在这里插入图片描述
IEEE 754标准对有效数字M和指数E,还有一些特别的规定
前面说过,1 <= M < 2,也就是说,M可以写成1.xxxxx的形式,其中xxxxx表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
至于指数E,情况就比较复杂
首先,E为一个无符号整数(unsigned int),这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047。但是,我们知道,科学计数法中E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成 10 + 127 = 137,即10001001。
然后,指数E还可以再分成三种情况

  • E不全为0或不全为1
    这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(1023),得到真实值,再将有效数字M前加深第一位的1。比如:0.5(1/2)的二进制形式为0.1,由于规定整数部分必须为1,即将小数点右移1位,则为1.0 * 2^(-1),其阶码为-1 + 127 = 126,表示01111110,而位数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则二进制表示为:
0 01111110 00000000000000000000000
  • E全为0
    这时,浮点数的指数E等于1 - 127(1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。
  • E全为1
    这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位S)。

示例代码分析

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值