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

一.数据类型的介绍

之前我们已经学习了基本的内置类型
char //字符数据类型 1个字节
short //短整型 2个字节
int //整形 4个字节
long //长整型 4/8个字节
long long //更长的整形 8个字节
float //单精度浮点数 4个字节
double //双精度浮点数 8个字节

c语言标准规定:siezof(long)>=sizeof(int)
类型的意义: 使用这个类型开辟内存空间的大小(大小决定了使用范围)
假设给一个变量,那么它总有一个范围,比如说年龄,是范围小的,使用像long等类型就明显不适合,使用int也明显大了,用short比较合适些。
我们可以使用<limits.h>的头文件,查看不同类型的取值范围

#include <limits.h>
int main()
{
	INT_MAX;//右击后点击转到定义
	return 0;
}

在这里插入图片描述
我们可以根据不同的取值范围来使用不同类型,这样内存利用率更高。

1.1类型的归类

1.整型家族

char
unsigned char
signed char
short
unsigned short [int]
signed short [int]
int
unsigned int
signed int
long
unsigned long [int]
signed long [int]

long long
unsigned long long [int]
signed long long [int]

注意:字符在存储的时候,存储的是ASCII码值,是整型

2.浮点数家族

float
double
long double

3.构造类型
也叫自定义类型,可以自己创建

数组类型 int arr1[10]–>int [10],char arr2[5]–>char [5]
结构体类型 struct
枚举类型 enum
联合类型 union

4.指针类型

int* pi;
char* pc;
float* pf;
void* pv;
以及结构体指针

5.空类型

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

二.整型在内存中的存储

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

int a = 10;//创建一个整型变量叫a,这时a向内存申请4个字节来存放数据

那么接下来则分析数据在所开辟的内存中是如何存储的

2.1介绍原码、反码、补码

整型数据的二进制表示形式有3种:原码、反码、补码
三种表示方法均有符号位数值位两部分,符号位都是用0表示“正”,用1表示“负”;
数值位:
正数的原、反、补码都相同。
负整数的三种表示方法各不相同。

原码:直接将数值按照正负数的形式翻译成二进制就可以得到原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。

例子:
int a = 10;
//00000000000000000000000000001010 原码
//00000000000000000000000000001010 反码
//00000000000000000000000000001010 补码
int b = -10;
//10000000000000000000000000001010 原码
//11111111111111111111111111110101 反码
//11111111111111111111111111110110 补码

对于整形来说:数据存放内存中其实存放的是补码
查看内存:
在这里插入图片描述
本质上内存中存放的是二进制,在VS上是为了方便展示,显示的是16进制

00000000000000000000000000001010 补码
0x 00 00 00 0a 1010的十进制是10,16进制是a

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

例子:

计算1-1,因为只有加法,所以是1+(-1)
00000000000000000000000000000001 -->1的原码
10000000000000000000000000000001 -->-1的原码
相加:10000000000000000000000000000010–>-2
所以原码计算是错误的

正确的方式:
00000000000000000000000000000001 -->1的补码
11111111111111111111111111111111 -->-1的补码
相加:00000000000000000000000000000000 -->0

2.2介绍大小端

前面我们发现存放的补码是倒着存的,为什么呢?这有关大小端
大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。

在这里插入图片描述
字节序 - 是以字节为单位,讨论存储顺序的

为什么有大端小端?

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则还是小端模式。

如何判断大小端:
我们可以简单举个例子,创建一个变量a=1,它的16进制为0x 00 00 00 01,我们已经知道了小端是把数据的低位保存到低地址中,大端是把数据的低位保存到高地址中,所以我们只要取低地址中的第一个字节,如果是01,就是1,如果是00,就为0。

int main()
{
	int a = 1;
	char* p = (char*)&a;//&a-->int*-->char*-->解引用
	if (*p == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

2.3有符号和无符号的区分(对于整型来说)

short == signed short
int == signed int
……
但是char 是signed char 还是unsigned char 不确定
在VS上是signed char

对于有符号数来说,它的最高位被当成符号位;对于无符号数来说,它把每一位都当成数值位。
在这里插入图片描述
所以对于char 类型,有符号的范围为-128~127;无符号的范围为0 ~255
可以再用一张图更好的表示:
在这里插入图片描述

2.4练习

(1)

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

a的原码是:10000000000000000000000000000001
反码是:11111111111111111111111111111110
补码是:11111111111111111111111111111111
因为是char类型,所以要发生截断,取后面的8个比特位
得:11111111
a是有符号类型的,所以整型提升补1,
得:11111111111111111111111111111111,这是提升后的补码
补码-1得到反码:11111111111111111111111111111110
反码再按位取反得到原码:10000000000000000000000000000001
所以a的结果为-1

c的原码是:10000000000000000000000000000001
反码是:11111111111111111111111111111110
补码是:11111111111111111111111111111111
因为是char类型,所以要发生截断,取后面的8个比特位
得:11111111
c是无符号类型的,所以整型提升补0,
得:00000000000000000000000011111111
最高位为0,是正数,正数原码、反码、补码相同,所以c的结果为255

因为char与unsigned char是一样的,所以b的结果也是-1

(2)

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

a的原码是10000000000000000000000010000000
反码是:11111111111111111111111101111111
补码是:11111111111111111111111110000000
因为是char类型,要截断,得:10000000
是有符号类型,补1:11111111111111111111111110000000
因为%u是以无符号的形式打印出来,所以原、反、补相同
a的结果为4294967168

b的原码是00000000000000000000000010000000
反码是:01111111111111111111111101111111
补码是:01111111111111111111111110000000
因为是char类型,要截断,得:10000000
是有符号类型,补1:11111111111111111111111110000000
因为%u是以无符号的形式打印出来,所以原、反、补相同
b的结果为4294967168

(3)

int i= -20;
unsigned  int  j = 10;
printf("%d\n", i+j); 
//按照补码的形式进行运算,最后格式化成为有符号整数

i的原码是:10000000000000000000000000010100
反码是:11111111111111111111111111101011
补码是:11111111111111111111111111101100

j的补码是:00000000000000000000000000001010

两个补码相加:11111111111111111111111111110110
反码:11111111111111111111111111110101
原码:10000000000000000000000000001010
所以结果为-10

(4)

unsigned int i;
for(i = 9; i >= 0; i--)
{
    printf("%u\n",i);
}

无符号的int 类型都是正整数,所以条件永远成立,死循环

(5)

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

变量i从0开始逐渐增加,a[ i ]=-1,-2,-3……-1000;
但是注意:char类型存放的范围是:-128~127,所以循环的是
-1、-2……-128、127、126……2、1、0 有256个数
-1、-2……-128、127、126……2、1、0 有256个数
strlen 是求字符串的长度,统计的是 \0 之前出现的字符个数, \0 的ASCII码值是0
所以结果为255
(6)

unsigned char i = 0;
int main()
{
    for(i = 0;i<=255;i++)
   {
        printf("hello world\n");
   }
    return 0;
}

unsigned char i 的范围为0~255,i = 0;i<=255条件恒成立死循环

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

浮点数即数学中的小数,例如:12.1234,3.14……
除此之外,还有1E10,表示1.0乘以10的10次方
浮点数家族包括: float、double、long double 类型
浮点数表示的范围:float.h中定义

3.1浮点型的存储规则

一个例子:

int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);//9
	printf("*pFloat的值为:%f\n", *pFloat);//0.000000
	*pFloat = 9.0;
	printf("num的值为:%d\n", n);//1091567616
	printf("*pFloat的值为:%f\n", *pFloat);//9.000000
	return 0;
}

上面例子中,说明以整型的形式存入数据,以浮点型的形式取出是不一样的;以浮点型的形式存入数据,以整型的形式取出是不一样的。

为什么会这样?因为整型和浮点型在内存中的存储方式是有差异的

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

(-1)^S * M * 2^E
(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。

比如:5.5是10进制的浮点数,它的二进制形式是101.1,相当于1.011*2^2
在这里插入图片描述

V = 5.5 = (-1) ^ 0 * 1.011 * 2 ^ 2
所以S=0,M=1.011,E=2

IEEE 754规定:
对于32位的浮点数(float),最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M
在这里插入图片描述
对于64位的浮点数(double),最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M
在这里插入图片描述

IEEE 754对有效数字M和指数E,还有一些特别规定。
前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位的,是节省1位有效数字。以32位将第一位的1舍去以后,等于可以保存24位有效数字。

指数E的情况就比较复杂
E为一个无符号整数(unsigned int),这意味着,如果E为8位,它的取值范围为0 ~ 255;如果E为11位,它的取值范围为0 ~ 2047
但是,E有可能出现为负数的情况:
0.5——10进制的浮点数
0.1——2进制的浮点数
V=(-1) ^ 0 * 1.0 * 2 ^ -1
即S=0,M=1.0,E=-1

所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。

在这里插入图片描述

还是以5.5的例子来说明:

int main()
{
	float f = 5.5;
	//101.1
	//1.011 * 2^2
	//(-1)^0 * 1.011 * 2^2
	//S=0,M=1.011,E=2 2+127=129
	//0 10000001 01100000000000000000000
	//01000000101100000000000000000000
	//0100 0000 1011 0000 0000000000000000
	//0x 40 b0 00 00
	return 0;
}

打开调试,内存中查看:
在这里插入图片描述
指数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(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)

回到最上面的例子:

int main()
{
	int n = 9;
	//00000000000000000000000000001001
	float* pFloat = (float*)&n;
	//0 00000000 00000000000000000001001
	//S=0   E=-126    M=0.00000000000000000001001 E全为0,等于1-127
	//(-1)^0 * 0.00000000000000000001001 * 2^-126
	printf("n的值为:%d\n", n);//9 以整型存进去,以整型取出来
	printf("*pFloat的值为:%f\n", *pFloat);//0.000000 非常小的值,所以只取小数点后面6位
	*pFloat = 9.0;
	//1001.0
	//1.001 * 2^3
	//(-1)^0 * 1.001 *2^3
	//S=0,E=3,M=1.001  3+127=130
	//01000001000100000000000000000000 存进去
	//取出是补码的形式,最高位为0,是正数,原码和补码相同
	printf("num的值为:%d\n", n);//1091567616 直接以%d的形式打印出来
	printf("*pFloat的值为:%f\n", *pFloat);//9.000000 以浮点型存进去,以浮点型取出来
	return 0;
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值