数据在内存中的存储(含面试题)

1. 整数在内存中的存储

在讲解操作符的时候,我们就讲过了下⾯的内容:有符号的整数的2进制表⽰⽅法有三种,即 原码、反码和补码三种表示方法均有符号位数值位两部分,符号位都是⽤0表⽰“正”,⽤1表⽰“负”,⽽数值位最⾼位的⼀位是被当做符号位,剩余的都是数值位。

正整数的原、反、补码都相同。
负整数的三种表示方法各不相同。
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。

#include <stdio.h>
int main()
{
	int a = 20;
	//00000000000000000000000000010100 -- 源码
	//00000000000000000000000000010100 -- 反码
	//0000 0000 0000 0000 0000 0000 0001 0100 -- 补码
	// 0    0    0    0    0    0    1    4 -- 16进制表示
	//0x00 00 00 14
	int b = -10;
	//10000000000000000000000000001010 -- 源码
	//0x80 00 00 0a
	//11111111111111111111111111110101 -- 反码
	//0xff ff ff f5
	//11111111111111111111111111110110 -- 补码
	//0xff ff ff f6
	return 0;
}

在这里插入图片描述
在这里插入图片描述
所以,对于整形来说:数据存放内存中其实存放的是补码。

为什么呢?
在计算机系统中,数值⼀律⽤补码来表⽰和存储。
原因在于,使⽤补码,可以将符号位和数值域统⼀处理;
同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
这些内容在在以前的博客中详细讲过了,点击此处即可查看

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

当我们了解了整数在内存中存储后,我们调试看⼀个细节:

#include <stdio.h>
int main()
{
	int a = 0x11223344;

	return 0;
}

调试的时候,我们可以看到在a中的 0x11223344 这个数字是按照字节为单位,倒着存储的。这是为什么呢?
在这里插入图片描述

2.1 什么是大小端?

其实超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为⼤端字节序存储和⼩端字节序存储,下⾯是具体的概念:

大端(存储)模式:是指数据的低位字节内容保存在内存的⾼地址处,而数据的高位字节内容,保存在内存的低地址处。

小端(存储)模式:是指数据的低位字节内容保存在内存的低地址处,而数据的高位字节内容,保存在内存的高地址处。

上述概念需要记住,方便分辨大小端。

画图演示:
在这里插入图片描述

2.2 为什么有大小端?

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

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为8bit 位,但是在C语⾔中除了8 bit 的char 之外,还有16 bit 的 short 型,32 bit 的 long 型(要看具体的编译器),另外,对于位数⼤于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于⼀个字节,那么必然存在着⼀个如何将多个字节安排的问题。因此就导致了⼤端存储模式和⼩端存储模式。

例如:⼀个 16bit short x ,在内存中的地址为 0x0010 x 的值为0x1122 ,那么 0x11 为⾼字节,0x22 为低字节。对于⼤端模式,就将 0x11 放在低地址中,即0x0010 中,0x22 放在⾼地址中,即 0x11 中。⼩端模式,刚好相反。我们常⽤的 X86 结构是⼩端模式,⽽ KEIL C51 则为⼤端模式。很多的ARM,DSP都为⼩端模式。有些ARM处理器还可以由硬件来选择是⼤端模式还是⼩端模式。

2.3 练习

2.3.1 练习1

请简述⼤端字节序和⼩端字节序的概念,设计⼀个⼩程序来判断当前机器的字节序。(10分)- 百度笔试题

#include <stdio.h>

int check_sys()
{
	int a = 1;
	//1. 取出a的地址
	//2. 强制类型转换成char*后解引用,只取第一个字节的数据
	//3. 如果取出的是1,就是小端,取出的是0就是大端
	return *(char*)&a;
}

int main()
{
	int a = 1;//0x00 00 00 01
	int ret = check_sys();
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("小端\n");
	}
	
	return 0;
}

运行结果如图:
在这里插入图片描述

2.3.2 练习2
#include <stdio.h>
int main()
{
	char a = -1;
	//10000000000000000000000000000001
	//11111111111111111111111111111110
	//11111111111111111111111111111111
	//存储在a中要发生截断
	//11111111 - a
	signed char b = -1;
	//11111111 - b
	unsigned char c = -1;
	//11111111 - c
	//00000000000000000000000011111111
	printf("a=%d,b=%d,c=%d", a, b, c);
	//%d - 十进制的形式打印有符号的整数
	//这里会发生整形提升,无符号数整形提升直接补0
	//
	return 0;
}

运行结果如图:
在这里插入图片描述

2.3.3 练习3
第一题
#include <stdio.h>
int main()
{
	char a = -128;
	//10000000000000000000000010000000
	//11111111111111111111111101111111
	//11111111111111111111111110000000
	//10000000 - a
	//打印时发生整形提升,有符号数按符号位提升
	//11111111111111111111111110000000
	//signed char取值范围: -128~127
	//unsigned char取值范围: 0~255
	printf("%u\n", a);
	//%u 是以十进制的形式打印无符号的整数
	return 0;
}

打开计算器可以观察到:
在这里插入图片描述

运行结果如图:
在这里插入图片描述

第二题
#include <stdio.h>
int main()
{
	char a = 128;
	//00000000000000000000000010000000
	//10000000 - a
	//打印时发生整形提升,有符号数按符号位提升
	//11111111111111111111111110000000
	printf("%u\n", a);
	return 0;
}

这里可以观察到和上一题一样

运行结果如图:
在这里插入图片描述

2.3.4 练习4
#include <stdio.h>
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	//arr[i] --> char -128~127
	//-1 -2 -3 -4 ... -1000
	//-1 -2 -3 ... -128 127 126 125 ... 5 4 3 2 1 0 -1 ...
	//128 + 127 = 255
	printf("%d", strlen(a));
	return 0;
}

运行结果如图:
在这里插入图片描述

2.3.5 练习5
第一题
#include <stdio.h>
unsigned char i = 0;
//0~255
//当i等于255时
//00000000000000000000000011111111 - i(255)
//00000000000000000000000000000001 - 1
//00000000000000000000000100000000 - i + 1
//00000000 - i + 1 由于char只占8个比特位,所以发生截断
int main()
{
	//死循环打印"hello world"
	for (i = 0; i <= 255; i++)
	{
		printf("hello world\n");
	}
	return 0;
}
第二题
#include <stdio.h>
#include <Windows.h>
int main()
{
	unsigned int i;
	//当i等于0时
	//00000000000000000000000000000000 - i(0)
	//10000000000000000000000000000001 - -1的原码
	//11111111111111111111111111111110 - -1的反码
	//11111111111111111111111111111111 - -1的补码
	//00000000000000000000000000000000 - i
	//11111111111111111111111111111111 - i - 1 

	for (i = 9; i >= 0; i--)
	{
		//死循环打印
		printf("%u\n", i);
		Sleep(1000);
	}
	return 0;
}

运行结果如图:
在这里插入图片描述

打开计算器可以看到
在这里插入图片描述

2.3.6 练习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;
}

运行结果如图:
在这里插入图片描述

画图分析:
在这里插入图片描述

由于,vs2022采用的是小段存储,所以,存放数据的时候低字节存放在地址处高字节存放在高地址处,故取出来时高位在右边低位在左边

通过监视也可以观察到
在这里插入图片描述

一个字节一个字节的显示情况如图
在这里插入图片描述

  • 23
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值