当前位置: 首页 > article >正文

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

文章目录

  • 前言
  • 一、整数在内存中的存储
  • 二、大小端字节序和字节序判断
    • 什么是大小端?
    • 为什么会有大小端?
    • 练习
      • 练习1
      • 练习2
      • 练习3
      • 练习4
      • 练习5
      • 练习6
      • 练习7
  • 总结


前言

  本篇是修炼内功的文章
  首先,你先明白一个事实,数据在内存中是以二进制的形式存储的
  然后正文开始!


一、整数在内存中的存储

  在讲解操作符的时候,我们就讲过了整数的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
代码如下:

#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

#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

#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

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

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

练习5

#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

#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

// 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* 指针,相当于跳过了一个字节,剩余的思考以图形式形象展现
在这里插入图片描述


总结

  写到这里的时候,已经是晚上了,本来还想写浮点数在内存中的存储的,但是我困了,睡觉!
  学到这里,你应该发现已经开始跟之前的知识串联起来了,如果你有这种感觉,很棒,继续加油!


http://www.kler.cn/a/303127.html

相关文章:

  • 鸿蒙next版开发:ArkTS组件点击事件详解
  • 结构体是否包含特定类型的成员变量
  • 大数据开发面试宝典
  • python高效处理大数据:将Excel10万数据分批插入MySQL数据库的实战代码
  • 开源vs闭源:你更看好哪一方?
  • 第二节 OSI-物理层
  • python如何将DICOM图片转为JPG?
  • Docker torchserve 部署模型流程
  • MATLAB | R2024b更新了哪些好玩的东西?
  • 在Excel中通过Python运行公式和函数实现数据计算
  • 计算机网络27、28——Linux命令1、2
  • 这款神器,运维绝杀 !!! 【送源码】
  • 内部flash模拟成EepRom-重新梳理
  • codeup:将已有文件夹推送到已有仓库
  • 计算机毕业设计 | SpringBoot+vue 游戏商城 steam网站管理系统(附源码)
  • 【运维监控】Prometheus+grafana+kafka_exporter监控kafka运行情况
  • Leetcode 3282. Reach End of Array With Max Score
  • 波场TRON领航者孙宇晨:区块链行业的青年先锋与标杆
  • 代理导致的git错误
  • Grafana面板-linux主机详情(使用标签过滤主机监控)
  • 如何使用ssm实现基于VUE3+SSM框架的在线宠物商城+vue
  • 【Java】StringUtils 工具类常用的方法
  • 【JavaSE】--方法的使用
  • 【vuetify】v-select 无法正常显示,踩坑记录!
  • 京东鸿蒙上线前瞻——使用 Taro 打造高性能原生应用
  • .net core 通过Sqlsugar生成实体