C语言整型数据在内存中的存储(22)

文章目录


前言

本篇是修炼内功的文章

首先,你先明白一个事实,数据在内存中是以二进制的形式存储的

然后正文开始!


一、整数在内存中的存储

在讲解操作符的时候,我们就讲过了整数的2进制表示方法有三种:原码、反码和补码

对于有符号数来说,三种表示方法均有符号位数值位两部分,符号位都是用 0 表示 '正' ,用 '1' 表示负,而数值位最高位的一位是被当作符号位,剩余的都是数值位

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

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

原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码

反码:将原码的符号位不变,其他位依次按位取反就可以得到反码

补码:反码 + 1就得到补码

并且,补码 -> 取反 -> + 1得到的就是补码

对于整型来说,数据存放内存中其实存放的是补码

为什么呢?我们再来回顾一下:

使用补码,可以将符号位和数值域统一处理;

同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需额外的硬件电路。

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

什么是大小端?

申请一个变量:int a = 0x11223344; // 占用4个字节

请问,这样在内存中有几种可能?答案是三种:

我们也思考,数据的存储,是未来将来正确的取出,而第三种方式随便放就显得很奇葩,很难记下来,不符合我们常用的习惯,假如你是C语言设计者,也不会选择这一种方式

而第一种叫做大端字节序存储 ,第二种叫做小端字节序存储

两者的区别都是在存储超过一个字节的数据显现的,其实说到底就是存储顺序的差别,按照不同的存储顺序,我们分为大端字节序和小端字节序存储

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

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

为什么会有大小端?

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

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

练习

练习1

请编写一个小程序来判断你的机器环境是什么字节序

有趣的是,这是一道面试题

方法也不难,我们想一个 int 有四个字节,假如来个取址符&,就指向低地址的那个字节,这时候强转char*指针再解引用,就得到了那个低地址的数据,若为小端,数据低位在低地址,即拿到了0x01,就是1;若为大端,数据低位在高地址,即拿到了0x00,就是0

代码如下:

c 复制代码
#include <stdio.h>  
#include <string.h>

int check_sys()
{
	int test = 0x00000001;
	
	return *((char*)&test); // 拿到低字节

}

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

	if (check_sys()) printf("是小端\n");
	else printf("是大端\n");

	return 0;
}

练习2

c 复制代码
#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;  
}

输出结果是 a = -1, b = -1, c = 255

我们来分析一下:

1.对于第一个有符号char,原码是10000000000000000000000000000001,反码是11111111111111111111111111111110,补码是11111111111111111111111111111111,然而我们现在要把补码放到比特里面,只能放八个比特位,也就是发生了截断,变成了11111111,同样其实b,c放得也是这样,现在对a来说,整型提升补的都是1,接着继续取反加1,这样就得到了-1的原码并打印

2.而对于c,整型提升成00000000000000000000000011111111,直接打印出255

%d是以十进制的形式打印有符号的整数

练习3

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

%u是十进制的形式打印无符号的整数

我们可以先看看有符号的char变量在内存中存储的所有可能性

一旦看到10000000,直接会被解读为-128,所以有符号char的取值范围是 -128 ~ 127

其实你观察一下11111111,再加个1,直接变成00000000,所以符号char是循环存储 的,请记一下

对于无符号char也是一样的

我们回到该题,-128在内存中的存储是

10000000000000000000000010000000 原码

11111111111111111111111101111111 反码

11111111111111111111111110000000 补码

所以截断后,a存储的是10000000

打印的时候,发生整型提升,提升的时候是按照自己的类型char提升

11111111111111111111111110000000,这时候,又以无符号的整数打印

就是一个很大的数了 -> 4,294,967,168

所以说,有符号的数尽量用%d打印,无符号的用%u来打印

练习4

c 复制代码
#include <stdio.h>  
int main()  
{  
    char a = 128;  
    printf("%u\n", a);  
    return 0;  
}

同上,分析后得出还是11111111111111111111111110000000,即4,294,967,168

练习5

c 复制代码
#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;  
}

理论上会有 -1 -2 ... -1000,可是我们之前说了,char取值范围是个循环,范围在-128-127

到0的时候,strlen读取到就会停止,最后会发现0前面有255个数字,所以答案是255

练习6

c 复制代码
#include <stdio.h>  

unsigned char i = 0;  

int main()  
{  
    for (i = 0; i <= 255; i++)  
    {  
        printf("hello world\n");  
    }  
    return 0;  
}

还是循环范围,unsigned char类型的 i 最大就是255,i循环到255后再自加,直接变成0,继续死循环

练习7

c 复制代码
// x86环境,小端字节序
#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;  
}

第一个应该很好想,主要是第二个,直接把地址强制转换为整数再加1,再强制转化为 int* 指针,相当于跳过了一个字节,剩余的思考以图形式形象展现


总结

写到这里的时候,已经是晚上了,本来还想写浮点数在内存中的存储的,但是我困了,睡觉!

学到这里,你应该发现已经开始跟之前的知识串联起来了,如果你有这种感觉,很棒,继续加油!

相关推荐
析木不会编程1 小时前
【C语言】动态内存管理:详解malloc和free函数
c语言·开发语言
达帮主1 小时前
7.C语言 宏(Macro) 宏定义,宏函数
linux·c语言·算法
茶猫_1 小时前
力扣面试题 39 - 三步问题 C语言解法
c语言·数据结构·算法·leetcode·职场和发展
初学者丶一起加油2 小时前
C语言基础:指针(数组指针与指针数组)
linux·c语言·开发语言·数据结构·c++·算法·visual studio
半盏茶香3 小时前
C语言勘破之路-最终篇 —— 预处理(上)
c语言·开发语言·数据结构·c++·算法
2401_858286113 小时前
118.【C语言】数据结构之排序(堆排序和冒泡排序)
c语言·数据结构·算法
Zer0_on11 小时前
数据结构栈和队列
c语言·开发语言·数据结构
一只小bit11 小时前
数据结构之栈,队列,树
c语言·开发语言·数据结构·c++
马浩同学12 小时前
【GD32】从零开始学GD32单片机 | DAC数模转换器 + 三角波输出例程
c语言·单片机·嵌入式硬件·mcu
一个没有本领的人12 小时前
win11+matlab2021a配置C-COT
c语言·开发语言·matlab·目标跟踪