C语言之数据在内存中的存储(1),整形与大小端字节序


目录

前言

一、整形数据在内存中的存储

二、大小端字节序

三、大小端字节序的判断

四、字符型数据在内存中的存储

总结



前言

     本文主要讲述整型包括字符型是如何在内存中存储的,涉及到大小端字节序这一概念,还有如何判断大小端,希望对大家有所帮助


❤️感谢支持,点赞关注不迷路❤️


(本文内容涉及到整形提升,如不了解,主页中可查看详细,两篇结合起来看更深入)

一、整形数据在内存中的存储

我们都知道。整数的2进制表示有3种,即原码、反码、补码

     

有符号的整数,三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表 示“负”,最高位的一位是被当做符号位,剩余的都是数值位。

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

正整数的原、反、补码都相同。

负整数的三种表示方法各不相同。

对于整形来说:数据存放内存中其实存放的是补码。

为什么呢?

     

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

(主页·位操作符有详细举例)


二、大小端字节序

运行以下代码,在vs(32位)调试窗口观察其在内存中的存储情况

#include <stdio.h>

int main()
{
	int a = 0x11223344;

	return 0;
}

内存调试窗口:

(0x0058FB3C是a在内存中的首地址)

发现:a = 0x11223344,它在内存中存储的却是 44 33 22 11,是倒着存储的。相信我们平时调试的时候肯定会有这样的疑问,这就涉及到了大小端字节序了。

什么是大小端?

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

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

解释说明:例如上面的 a = 0x11223344,我们按照数学方式读这个数时,44是不是个位和十位。它相比于前面的 112233 是不是算低位。44 就是一个低位字节内容,VS是以小端模式存储数据的,所以 44 就存储在内存的低地址处,其余的就按顺序往高处存储,这样我们看到的就是 44 33 22 11了。那么假如我们用的是大端模式存储数据的,那么我们看到的就是 11 22 33 44 ,11在内存中对应的是低地址,44 是高地址。

为什么有大小端?

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

    

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

     

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


三、大小端字节序的判断

判断大小端其实很简单,定义 int a = 1,16进制为 0x 00 00 00 01,我们只需要访问其首地址对应的字节内容即可,小端会以 0x 01 00 00 00 从低地址往高地址排放,大端会以 0x 00 00 00 01 从低地址往高地址排放。

如下,一小段代码即可:

#include <stdio.h>

int check_sys()
{
	int a = 1;

	return *(char*)(&a);
}

int main()
{
	int ret = check_sys();

	if (ret == 1)
	{
		printf("小端\n");
	}
	else if (ret == 0)
	{
		printf("大端\n");
	}

	return 0;
}

运行结果(VS):


四、字符型数据在内存中的存储

字符型其实也属于整形范畴,存储的是其ASCII码值。

我们可以先观察以下代码:

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

运行结果:

解疑:

  1. 首先我们知道,字符型变量以%d打印时是要发生整形提升的。
  2. 然后a为char类型,vs中,char类型默认为有符号字符型,也就是等同于 signed char,所以a整形提升是要看符号位的,-1的补码是11111111 11111111 11111111 11111111,a大小只有一个字节,存储时会发生截断取后8位,a其补码为11111111,整形提升后 11111111 11111111 11111111 11111111,以%d打印的是原码,再转为原码为10000000 00000000 00000000 00000001,因此打印的还是-1
  3. b与a一样,然后就是c,c是无符号字符形,c的补码还是1111111,整形提升,无符号整形提升高位补0,也就是 00000000 00000000 00000000 11111111,再转为原码,因为是无符号整形,原反补相同,所以原码还是 00000000 00000000 00000000 11111111,打印出来就是255。


在看这段代码:

#include <stdio.h>

int main()
{
	char a = -128;

	printf("%u\n", a);
	printf("%d\n", a);

	return 0;
}

运行结果:

解疑:

  1. 我们发现以%u(无符号整形)方式打印时,打印出的是一个非常大的数字,以%d打印还是原数值,那么为什么会这样呢
  2. 首先,a以%u打印时,还是会发生整形提升,-128有点大,我们先算出-128的原码为  10000000 00000000 00000000 10000000,取反加1算出-128的补码为 11111111 11111111 11111111 10000000,截断后 a 的补码就是10000000,以%u打印,虽然是以无符号整形打印,但是整形提升时是根据原类型进行提升的,原类型为char,有符号字符型,所以高位补符号位1,即 11111111 11111111 11111111 10000000。然后%u就发挥作用了,要把整形提升后的这个补码看成无符号整形,这时候原反补就相同,原码就是 1111111 11111111 11111111 10000000,打印出来的结果就是上图中很大的数字,我们可以借助计算器验证:
  3. 以%d打印时接着2中整形提升后的补码 11111111 11111111 11111111 10000000,这里是以%d打印,所以要看成有符号的整形,其原码就要进行取反加1,即 10000000 00000000 00000000 10000000,打印出来就是-128


再看这段代码:

#include <stdio.h>

int main()
{
	char a = 128;

	printf("%u\n", a);
	printf("%d\n", a);

	return 0;
}

运行结果:

解疑:

  1. 我们发现128的结果与-128的结果相同,这又是为什么
  2. 其实我们自己再重新算一下就会发现,a存储时补码都是 10000000,因为128与-128的补码后8位是完全相同的,截断时值就相同。算一个特殊情况,因为a的值相同,所以后续以%u或者%d打印时效果也相同。


通过以上例题,我们可以再推导一下char型变量(有符号)的存储范围为啥是 -128~127了,还有 unsigned char 为啥是 0~255

我们画图分析:

如此,我们可以画成一个圆:

这就是 char 类型为什么存储范围是-128~127,哪怕赋值的数字超过这个范围,也会被截断在这个范围内。

这就是unsigned char 存储范围为什么是0~255。

其实,signed short 和 unsigned short 类型数据也可以画圆圈表示,int也可以,这里如果感兴趣可以自己画着试试。同上即可


 

总结

        以上就是本文的全部内容,希望对你有所帮助。

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-09 19:24:02       53 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-09 19:24:02       55 阅读
  3. 在Django里面运行非项目文件

    2024-07-09 19:24:02       46 阅读
  4. Python语言-面向对象

    2024-07-09 19:24:02       56 阅读

热门阅读

  1. 如何高效学习(二)

    2024-07-09 19:24:02       30 阅读
  2. lvs集群(一)

    2024-07-09 19:24:02       28 阅读
  3. 使用Zookeeper实现分布式锁的最佳实践

    2024-07-09 19:24:02       25 阅读
  4. 每天一个数据分析题(四百十一)- 主成分

    2024-07-09 19:24:02       25 阅读
  5. python教程---网络爬虫

    2024-07-09 19:24:02       27 阅读
  6. Emacs 的优点及与 DE 的比较

    2024-07-09 19:24:02       21 阅读
  7. react VS vue

    2024-07-09 19:24:02       22 阅读
  8. Qt使用笔记1(智能指针,deleteLater,多屏)

    2024-07-09 19:24:02       28 阅读