数据在内存中的存储

目录

1.整数在内存中的存储

 2.大小端字节序和字节序判断

2.1什么是大小端

2.2为什么有大小端

2.3练习

练习1

练习2

练习3

练习4

练习5

练习6

3.浮点数在内存中的存储


1.整数在内存中的存储

原码、反码、补码

符号位和数值位,0正,1负,数值位最高位是符号位,剩下数值

正数原反补都相等

原:直接按照正负号和相应的数值以二进制形式呈现

反:原码符号位不变,数值位取反

补码;反码+1是补码

整型数据存放在内存中是以补码形式存储,补码可以统一处理符号位和数值位

由于cpu只有加法,所以补码还能统一处理加法和减法,由于运算过程相同,原码和补码不需要额外的电路就可以实现

 2.大小端字节序和字节序判断

一组数据,在内存中以字节为单位,倒着存储

2.1什么是大小端

超过一个字节的数据在内存中存储的时候,有存储顺序的问题

按照不同存储顺序,分大端字节序存储和小端字节序存储

大端:数据的低位字节内容保存在内存的高地址处,数据的高位字节内容,保存在内存的低地址处

小端:数据的低位字节内容保存在内存的低地址处,数据高位字节内容,保存在内存的高地址处

2.2为什么有大小端

内存的单位是字节,但1个字节只有8bit,但除了char是8bit外,其他大多都不是8bit,比如int的32bit。大于8位的处理器,寄存器宽度大于一个字节,那必然要考虑多个字节顺序的问题

举例:
16bit的short类型x
内存的起始地址是0x0010
由于是16bit,也就是两个字节
第一个字节地址是0x0010,第二个字节地址是0x0011
x的值设为0x1122
因为是16进制,2个16进制位刚好是8bit,那么高字节是0x11
低字节就是0x22
大端模式,就是高字节0x11放入地址0x0010
低字节0x22放入地址0x0011
小端模式,刚好相反。
常用x86结构式小端,keil c51是大端,许多arm和dsp是小端
有些ARM处理器是由硬件选择是大端还是小端

2.3练习

练习1

#include<stdio.h>

int check_sys()
{
	int i = 1;//用简单的数字
	return (*(char*)&i);//取第一个字节的内容,如果直接用(char)i;
//会发生截断,但截断针对的是有数值的部分,不管是大端还是小端,都先截断01的部分
//其他部分都不会计入
}
int main()
{
	int ret = check_sys();
	if (ret == 1)//由于1只用1个字节,其他3个字节都是0
//有数值的那个字节要么是开头,要么是结尾,对应小端或大端
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

练习2

char类型默认是signed(取决于编译器)也就是有符号位的

由于-1是整型类型,32bit,但char只有8bit,发生截断,从低位数据开始截断8bit

100000000000000000000000000

1111111111111111111111111111110

1111111111111111111111111111111

因此是a,b,c存的数据内容都是11111111

整型数据在内存中存储是以补码形式

%d是以十进制形式,打印有符号整型

这里需要整型提升,由于char是有符号位,且符号位是1,所以a整型提升的时候,前面补24个1,1111111111111111111111111111111,然后补码变原码

b也是同理,整型提升后也是1111111111111111111111111111111,然后补码变原码打印

c不一样,c是无符号整数,因此补0,变成000000000000000000111111111,由于是正数补码,所以原码相等,因此打印255。

如果是有符号位的,那么-1要先从补码变成原码,最终结果是-1

但如果没有符号位,那么默认是正数,那么补码-1等于255,所以结果是255

练习3

#include<stdio.h>

int main()
{
	char a = -128;
	printf("%u\n", a);
	return 0;
}
char类型的取值范围,如果是有符号位,则-128~127

10000000000000000000000010000000是-128原码
11111111111111111111111101111111是反码
11111111111111111111111110000000是补码
发生截断之后,a存的就是10000000,10000000是直接识别为-128
因此再以无符号整数形式打印后,先整型提升变成
11111111111111111111111110000000,然后由于是无符号
所以直接打印,也就是32位二进制数的十进制减去127等于
4294967168
#include<stdio.h>

int main()
{
	char a = 128;
	printf("%u\n", a);//u是无符号整数
	return 0;
}

首先是128的原码:00000000000000000000000010000000
反码:01111111111111111111111101111111
补码:01111111111111111111111110000000
放入char类型中发生截断,则是10000000
然后以无符号整数打印前先整型提升,变成:
11111111111111111111111110000000
然后直接打印,则是4294967168

练习4

#include<stdio.h>

int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
//内容从-1开始,一直-1,但是char类型的取值范围是-128~127
所以,一直-1,会变成0,由于\0的ascll码就是0
	}
	printf("%d", strlen(a));//所以strlen(a)就是255
//另外,由于0-1又是-1,所以会继续轮回,直到遍历数组
	return 0;
}

练习5

#include<stdio.h>

unsigned char i = 0;
int main()
{
	for (i = 0; i <= 255; i++)
	{
		printf("hello world\n");
	}
	return 0;
}
//由于char无符号类型的取值范围是0~255
当i=255后执行一次,然后i++,i=256,但会溢出,高位舍掉,只剩8个0了
所以i又变成是0了,于是继续执行循坏,造成了死循环
#include<stdio.h>

int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
	return 0;
}
//首先无符号整型,不能放负数
所以当i=0执行一次后,i--;
按理来说是-1,但实际上0的二进制是下面
00000000 00000000 00000000 00000000
-1,那么根据进位原则,需要向第33位借2
再-1,就变成11111111 11111111 11111111 11111111
由于是按照无符号整型打印,所以,结果就是32位二进制的值
4294967295

练习6

#include<stdio.h>
int main()
{
	int a[4] = { 1,2,3,4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);
	printf("%x,%x", ptr1[-1], *ptr2);
	return 0;
}

//这里用小端的x86
//&a+1就是跳过了整个数组,也就是4这个元素之后的地址
//将这个地址的类型从数组指针强制变成整数指针
//ptr[-1]=*(ptr1+(-1))
由于前面已经强制转换类型了,所以这里-1,只会向前跳过一个元素
而不是整个数组
最终ptr1[-1]的值就是4

//a代表首元素地址,也就是首元素第一个字节的地址,强制转换类型int后
+1,再转换成整型指针,就是跳过了一个字节,也就是首元素第二个字节的地址
然后解引用,因为整型是4个字节,所以会涵盖第二个元素的第一个字节
又由于是小端模式,所以低字节放低地址
解引用的第一个到到第3个字节都是00,但第4个字节是02
(因为第二个元素16进制形式是0x00 00 00 02)
所以转换成16进制形式后就是0x02 00 00 00
%x就是以16进制形式形式输出整数
最终*ptr2就是2000000

3.浮点数在内存中的存储

 浮点:float、double、long double

范围在float.h头文件中

IEEE754规定了二进制浮点数公式:

v=(-1)^S * M * 2^E

1.01*2^2

s决定了正负号

m是1.01,且大于等于1,小于2(因此存储的时候1是省略的,读的时候再添加)

2^E就是指数位,比如1.01*2^2=101

32位浮点数,最高位1位存S,后面8位存E,剩下23位存M(因为1省略,所以这里存的都是小数部分)

64位浮点数,最高位1位存S,后面11位存E,剩下52位存M(因为1省略,所以这里存的都是小数部分)

由于E可以是负数,但E本身存储时又是无符号整数,所以加一个中间值,就可以避免出现存储负数,32位中间值是127,64位是1023

如果e不全为0或不全为1
十进制0.5的二进制是0.1
由于正数部分必须为1,小数点向右移
1.0*2^(-1)(这是二进制形式)
s是0,e是-1+127,M存储0,由于还有22位空的,补0进去
于是二进制存储时:0 01111110(是十进制127) 000000000000000000000(23个0)

e存的都是0
那么e是-126(1-127)或者-1022(1-1023)
假设是0 00000000 0010000000000000
那么就表示成(如果s是1,则是1*后面的东西)0.001*2^-126

e存的都是1
真实E是128
那么是1.xx*2^128
表示+-无穷大

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

9的二进制是00000000000000000000000000001001(原码/补码)

根据%d打印N是9,很正常,但如果按照%f,则要按照上面的方法来

0 00000000 0000000000000000000001001

则(-1)^0 * 0.0000000000000000000001001*2^-126==0.000000(float只打印小数点6位)

下面,以浮点数形式存储9.0

(-1)^0 * (1.001) *2^3

则是0 1000010 00100000000000000

则是直接打印0100001000100000000000000的十进制形式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值