数据在内存里的存储(1)【整数在内存中的存储】【什么是大小端】

一.整数在内存里的存储

我们都知道,关于整数的二进制表示方法有三种,原码,反码和补码。而正数的原码,反码,补码都相等。而负数的表示方法各不相同。原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。反码:除了负数的符号位不变,其他位都取反就行。补码:反码加一就是补码。

对于整型来说,数据存放内存中其实存放的是补码。为什么用补码来存放呢?

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

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

1.什么是大小端?

在超过一个字节的数据在内存中存储的时候,就会出现存储顺序的问题。按照顺序,我们就会有大端字节序存储和小端字节序存储。

这里可以看到,我给a的值是0x11223344。但是在内存中这个方向就完全反了过来。变成了44332211。这个就是我们说的小端字节存储。

小端字节序存储:把一个数据的低位字节的内容存储到低地址处,而高字节的的内容存储到高字节处。

大端字节存储:把一个数据的高位字节的内容存储到低地址处,而低字节的的内容存储到高字节处。

大小端存储跟计算机架构有关。我用的是vs2022,用的是小端存储。这就是大小端存储的基本知识。

2.为什么会有大小端?

因为在计算机系统中,我们是以字节为单位的,每个内存单元都对应着一个字节,而一个字节对应八个bit位,但是在c语言中除了八个bit位的char外,还有16位的short,32位的int(具体要看编译器)。另外,对于位数大于8位的处理器,例如16位和32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个将多个字节安排的问题。因此有了大小端存储。(x86结果是小端模式,而KEIL  C51为大端模式。很多的ARM,DSP都为小端模式。但有些ARM处理器还可以由硬件来选择大端还是小端)

三.一些练习

1.练习1

设计一个代码来判断当前机器的字节序

#include<stdio.h>
int main()
{
	int a = 1;
	if (*(char*)&a == 1)//注意这里我们是把a的地址给强制转化成char*,解引用的时候其实就访问一个字节
		                //大端存储就是0,小端存储就是1
		printf("小端\n");
	else
		printf("大端");
	return 0;
}

这里我的是小端存储,打印出来的也成功是小端。

2.练习2

不仅是大小端,关于数据的存储也有一些练习,大家来了解一下。

#include<stdio.h>
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;
}

可以思考一下这三个值等于多少。

我们知道关于负数在内存中的存储是补码。注意:单独的一个char  到底是有符号还是无符号是取决于编译器的,这里我用的vs2022是是有符号的。先来看最后打印的是整型。那么这里的a,b,c是要整形提升的。(无符号补0,有符号补1)

#include<stdio.h>
int main()
{
	char a = -1;
	//1000000000000000000000000000001   -1的原码
	//1111111111111111111111111111110   -1的反码
	//1111111111111111111111111111111   -1的补码
	//因为是char类型,所以咱们就只要11111111
	//这里要整形提升,所以是1111111111111111111111111111111,按照步骤,求出原码就是-1
	signed char b = -1;
	//11111111也是-1
	//跟a的步骤一样
	unsigned char c = -1;
	//11111111也是-1
	//这个是无符号整形提升后是00000000000000000000000011111111
	//这个就是255
	printf("a=%d  b=%d  c=%d", a, b, c);
	//        -1    -1   255
	return 0;
}

取值范围:无符号的是0~255。有符号的是-128~127。

3.练习3

再来看一个代码

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

来看解析

#include <stdio.h>
int main()
{
	char a = -128;
	//原码:100000000000000000000000010000000
	//反码:111111111111111111111111101111111
	//补码: 111111111111111111111111110000000
	//因为是char类型,所以只有10000000-a
	printf("%u", a);
	//这里是用%u来打印,所以要整型提升
	//11111111111111111111111111111110000000
	//因为是无符号数,所以就不会存在什么原码反码和补码,直接把这个东西转化成十进制数就是答案
	//是一个很大的数字
	return 0;
}

输出:4294967168

那我把-128换成128来弄,大家来想一下是什么结果呢?

4.练习4

这个代码就很有意思了。

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

来看个图

当我们从0开始一直加,加到127的时候,已经到了最大数的限制。这时再去加一直接就变成了-127,等加到-1,又开始了一次循环。当然这是有符号的char类型。

那么像是这个题我们就好求了。strlen找的是'\0'之前的元素个数。

所以a[i]就是-1 -2 -3 ...-127  -128 127  126...4 3 2 1 0 -1...

看看有几个元素就行了。

5.练习5

前面的太简单了,上点强度。

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

来看解析

#include <stdio.h>
int main()
{
	int a[4] = { 1,2,3,4 };
	int* ptr1 = (int*)(&a + 1);
	//&a的类型是int(*)[4]是一个数组指针,所以前面有一个强制转换。
	//这里有+1跳过的是整个数组,ptr1[-1]的意思就是*(ptr1-1),往后移动一个sizeof(int)的距离,结果就是4.
	int* ptr2 = (int*)((int)a + 1);
	//这里我直接是把a强制转换成int类型了,现在的a就是个整数,整数加一,那就是加一
	//比如像是这里的a在内存中的存储:01 00 00 00   02 00 00 00   03 00 00 00   04 00 00 00 
	//整数a加一,跳过了一个字节,本来位置在01的a,现在位置在00了
	//而现在又强转成了int*类型,一次访问4个字节,所以真正访问的是00 00 00 02
	//我用的是小端存储,所以真正打印出来的就是02000000
	printf("%x,%x", ptr1[-1], *ptr2);
	return 0;
}

最终打印:4,2000000

当然在内存中肯定不止整数的存储,当然还有小数。下篇博客我来介绍一下小数在内存中的存储。感谢大家的观看,如有错误,请大家多多指正。

  • 19
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值