数据在内存中的存储

本文详细解析了整数在内存中的三种表示方式(原码、反码、补码),以及为何内存中使用补码存储。同时介绍了大小端字节序的概念及其在计算机系统中的作用,包括大端和小端存储模式的特点。文章还涵盖了浮点数的表示、存储规则以及相关示例。
摘要由CSDN通过智能技术生成

整数在内存中的存储

整数的二进制表示方式有三种:原码,反码,补码,三种方法均有符号位(0:正,1:负)和数值位,最高位为符号位,其余都为数值位,正数的原码,反码,补码相同。负数的原码直接将数值按正负数形式翻译成二进制,反码为原码的符号位不变,其余按位取反,补码为反码+1。对于整形来说,数据存放在内存中为补码,但是。。。为什么呢?

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

大小端字节序判断

一、概念

什么是字节序嘞?名表其意:字节的顺序。当数值超过1个字节时,存储在内存中的顺序就需要确定,例:0x11223344

在大端字节序(将一个数值的低位字节序的内容存到高地址处,高位字节序的内容存放在低地址处)下,它是这样存储的:

低地址      11  22  33  44             高地址

 在小端字节序(将一个数值的低位字节序的内容存到低地址处,高位字节序的内容存放在高地址处)下,它是这样存储的:

低地址       44  33  22  11             高地址

大小端的名字还有起源呢,这一切要从一个深奥的问题:剥鸡蛋要从大头还是小头剥说起。。。(格列夫游记的一次历史性会议讨论了这件事)

二、意义

为什么要存在大小端呢?

因为在计算机系统中,以字节为单位,每个地址单元对应着一个字节,一个字节占8个bit位,在C语言中有8bit的char,16bit的short,32bit的int,对于位数>8的处理器(16位、32位)由于寄存器宽度>1byte,必然存在一个如何将多个字节安排的问题,这就推动了大小端存储模式的形成

小端存储:X86,很多ARM,DSP     大端存储:KEIL,C51
大端模式的优点:大端模式中地址的变化顺序(低到高)与数据的阅读顺序(高位到低位)吻合。当沿着地址空间找到某个数据内存的时候,可以根据他的内容的第一个字节来判断正负。

小端模式的优点:强制类型转化,可以直接取出前面的低位两字节。CPU计算是从数据的低位到高位计算,效率高。CPU从地址的低位到高位移动的时候,正好数据也是从地位到高位变化,计算高效。

三、实例

(1)

实战环节:简述大小端字节序的概念,设计一个小程序判断当前机器的字节序(百度笔试题):

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

封装到函数中:

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

简化可写成:

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

 tips:

万万不可return(char)a;

强转截断低位,截断时都是01。

(2)
#include<stdio.h>
int main()
{
	char a = -1;
	signed char b = -1;
	unsigned char c = -1;
	printf("a=%d b=%d c=%d\n", a, b, c);
	return 0;
}

猜猜输出结果是什么? 

为什么是这样的输出结果捏?我们先来了解一下char类型:

char类型比较特殊,是signed char还是unsigned char取决于编译器,在VS上,char为signed char。而对于int类型,int就是signed int,unsigned int自己作为一个类型。

char开辟八个bit位存放数据,存储时发生截断

-1

原码

10000000  00000000 00000000 00000001

反码

11111111 11111111 11111111 11111110

补码

11111111 11111111 11111111 11111111

截断存储后:

a:11111111

b:11111111

c:11111111

%d:以十进制形式打印有符号整形-->发生整形提升,整形提升高位补符号位

整形提升后a,b补码相同,都为:

 11111111 11111111 11111111 11111111

a,b转成原码后结果为-1

无符号整形在整形提升时高位补0

c补码:

00000000 00000000 00000000 11111111

所以结果为:255

简单计算一下:假设+1,结果就为 100000000(二进制)==2^8==256。

256-1=255==11111111(二进制)

 (3)
#include<stdio.h>
int main()
{
	char a = -128;
	char b = 128;
	printf("%u\n", a);
	printf("%u\n", b);
	return 0;
}

输出?

 

在这里解释下,%u是打印无符号整数

-128

原:10000000  00000000  00000000  10000000

反:11111111  11111111  11111111  01111111

补:11111111  11111111  11111111  10000000

截断后存于a中:10000000

128

原:00000000  00000000  00000000  10000000

反:00000000  00000000  00000000  10000000

补:00000000  00000000  00000000  10000000

截断后存于b中:10000000

打印时发生整形提升

最高位补符号位,即补1

补完后:11111111  11111111  11111111 10000000

11111111  11111111  11111111 10000000转化成十进制数:就是4294967168

不信你看:

接下来介绍一下char类型的取值范围:

char ---->  8bit

补码们:

0000 0000-->0

0000 0001-->1

...

...

0111 1111-->127

1000 0000-->1111 1111-->10000 0000(溢出了,规定为-128)

1000 0001-->1111 1110-->1111 1111-->-127

1000 0010-->1111 1101-->1111 1110-->-126

...

...

1111 1110-->1000 0001-->1000 0010-->-2

1111 1111-->1000 0000-->1000 0001-->-1

综上,char类型的取值范围为:[-128,127]

(4)
#include<string.h>
#include<stdio.h>
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	return 0;
}

strlen遇到\0即止,这是一个轮回:

 

(5)
#include<stdio.h>
unsigned char i = 0;
int main()
{
	for (i = 0; i <= 255; i++)
	{
		printf("hello world!!");
	}
	return 0;
}

结果:死循环(unsigned char取值范围为0~255,判断条件永远符合)

(6)
#include<stdio.h>
int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("hello world!");
	}
	return 0;
}

死循环+1

(7)
#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下执行结果是什么? 

解释:

ptr1:

ptr1[-1]-->*(ptr1+(-1))-->*(ptr1-1)

ptr2:

假设数组首元素地址:0x12ff40 

0x0012ff40+1-->1244992+1-->1244993-->0x0012ff41

*ptr2:由于小端:0x 02 00 00 00

还是这段代码,x64运行就崩了:读取访问权限冲突:

为什么捏?

x86:指针大小-->4字节

x64:指针大小-->8字节

a是数组名,是数组首元素的地址,地址在64位环境下的大小就是8字节,强转成int必会发生截断

假设a:0x0012ffcd30542320-->0x30542320-->非法访问

若想让他正常,怎么改捏?

改成:

int *ptr2=(int*)((long long)a+1);

就好啦! 

浮点数在内存中的存储

一、概念

浮点数类型:

1.float

2.double

3.long double-->C99植入

我们知道,sigend char的取值范围是-128~127,unsigned char的取值范围:0~255,signed short的取值范围:-32768~32767,unsigned short的取值范围:0~65535,这些整形的取值范围定义在一个头文件下:limits.h

浮点数的表示范围也定义在一个头文件下: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;
}

输出结果?

解释概念

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

V=-1^{S}*M*2^{E}

(-1)^s表示符号位,当s=0时,V为正数,当s=1时,V为负数

M表示有效数字,(M>=1&&M<2)

2^E表示指数位

V=5.5(十进制)=101.1=1.011*2^2

很好理解吧。。。拿十进制记:

V=123.45=1.2345*10^2

存储

对于32位浮点数,最高位为符号位S,接着的8位存指数E,剩余的23位存有效数字M

对于64位的浮点数,最高的一位存符号位S,接着的11位存指数E,剩下的52位存有效数字M

对于有效数字M和指数E,还有一些特殊规定:

IEEE754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保留后面的部分,能节省一位有效数字

对于指数E,情况较为复杂:

首先,E是无符号整数(unsigned int),若E为8位:0~255(中间数:127),若E为11位:0~2047(中间数:1023)

但是E可以是负数:

IEEE754规定:存入内存时,E的真实值必须再加上一个中间数

例:

5.5(十进制)==(-1)^0*1.011*2^2(二进制)

V=-1^{S}*M*2^{E}

S=0,M=1.011,E=2

存储:

0  1000 0001   0110 0000 0000 0000 0000 000

1000 0001-->2+127转成二进制

取出

E从内存中取出分三种情况:

1.E不全为0,不全为1

2.E全为0

3.E全为1

(1)

E全为0:

为了表示很小的数字-->\pm 1*x*2^{-127}

这时规定E=1-127(或1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxxxx的小数,这样表示\pm 0和接近0的很小的数字

(2)

E全为1:

若有效数字M全为0,则表示无穷大(正负取决于符号位S)

题解

那么这道题该如何解释呢?

9的补码:

0000 0000 0000 0000 0000 0000 0000 1001

在浮点数看来:

0   00000000   00000000000000000001001

情况:E全为0

(-1)^0*0.000...0001001*2^{-126}

%f:打印小数点后6位,打印出为0.000000

*pfloat=9.0

转成二进制:1001.0-->-1^0*1.001*2^{3}

存:

0   1000 0010    0010 0000 0000 0000 0000 000

打印时打印原码(正数原反补相同)

二进制转十进制-->结果即为1091567616 

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值