• 数据在内存中是如何存储的?(上)


    一. 整型数据的二进制表示

    整型数据的二进制表示形式有三种:原码,反码,补码
    1.原码:根据类型来表示二进制位数,最高一位为符号位(1表示负数,0表示正数)
    2.反码:原码的符号位不变,其余按位取反;
    3.补码:反码 + 1

    注意一点:正数和负数的原码,反码,补码有区别!!!
    正数:原码,反码,补码是相同的。
    负数:原码,反码,补码按上面条件改变

    上面三种是怎样表示呢?举一个简单例子

    `创建一个整型变量num,在内存中开辟了四个字节的空间存放数据,四个字节 = 32 个比特位,也就是32位二进制

    int num = 10;

    原码
    00000000000000000000000000001010
    反码
    00000000000000000000000000001010
    补码:
    00000000000000000000000000001010

    int num = -10;
    原码:
    10000000000000000000000000001010
    反码:(符号位不变,其余按位取反)
    1111111111111111111111111111111110101
    补码:(反码 + 1)
    1111111111111111111111111111111110110

    注意三点:
    1.内存中存储的是补码。

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

    2.在内存中用一般用十六进制来表示的。

    因为1个十六进制位可以表示4个二进制位,那在内存查看变量时,只用看(32 /4)8位即可,方便查看。
    在这里插入图片描述

    3.内存中补码是倒着存储的。
    在这里插入图片描述

    二.数据类型详细介绍

    C语言有哪些基本数据类型?

    char         //字符数据类型,      1个字节大小(以64平台为例)
    short        //短整型             2个字节大小
    int          //整型               4个字节大小
    long long    //更长的整型         8个字节大小
    float        //单精度浮点型       4个字节大小
    double       //双进度浮点型       8个字节大小
    char*        //字符型指针类型     8个字节大小
    int*         //整型指针类型       8个字节大小
    double       //双精度型指针类型   8个字节大小
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    1.1 类型的基本归类

    整型家族

    char
        unsigned char
        signed char
    short
        unsigned short
        signed short
    int 
        unsigned int 
        signed int
    long
        signed long
        unsigned long
    long long
        unsigned long long
        unsigned long long
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    为什么char类型属于整型呢?
    因为字符存储的时候,存储的是ASCII码值,是整型,所以在归类位整型家族

    浮点型家族:

    float
    long float
    double
    long double
    
    • 1
    • 2
    • 3
    • 4

    指针类型家族:

    int*
    char*
    float*
    double*
    void*
    
    • 1
    • 2
    • 3
    • 4
    • 5

    空类型:

    void //表示空类型(无类型)
         //一般用于函数的返回类型,函数的参数,指针类型
    
    • 1
    • 2

    构造类型:(自定义类型和变量)

    数组类型 类型 数组名[]
    结构体类型 struct
    枚举类型 enum
    联合类型 union

    1.2认识有无符号的区别( signed 和 unsigned )

    对于整型家族的类型来说,有符号和无符号是由区别的,不同的的编译器识别在区分char时也所有不同,有些是定义成signed
    char,有些是定义成unsigned char; char 在VS2019上是 signed char。但是可以确定的是,

    short == signed short;int ==signed int等

    signed char 和 unsigned char的区别是什么呢?

    signed char
    
    • 1

    我们知道char类型是一个字节(8个比特位)
    假设它的二进制位是:01010111
    首位就是它的符号位。
    下图是八个比特位存放在二进制中的所有可能,因为首位是符号位,所以我们由此可知,
    sigened char的取值范围是 -128 ~ 127而且只会在这个范围,超出的部分进行下一次循环。
    在这里插入图片描述
    在这里插入图片描述

    unsigned char
    
    • 1

    二进制的每一位都是数值位,没有符号位。
    假设unsigned char的二进制位是:10010101
    则unsigned char八比特位二进制位取值范围:0 ~ 255
    同理,即使数值递增也不会超出这个范围,超出部分进行下一次循环。

    在这里插入图片描述

    在这里插入图片描述

    理解到这里,我们看些代码强化一下,尤其注意无符号类型(unsigned)。

    1.3代码理解一:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    char a变量值的是-1;先用32位二进制位求出补码。
    原码:10000000000000000000000000000001
    反码:11111111111111111111111111111110
    补码:11111111111111111111111111111111
    但因为是存放char类型中,只能取8个bite位,从补码后面截断8个比特位,得到11111111
    但是用%d(十进制的形式打印有符号整型整数)形式来打印,所以发生了整型提升(a是有符号类型,所以用符号位的数补全二进制位32位数)得到:
    补码:11111111111111111111111111111111
    反码: 11111111111111111111111111111110
    原码:10000000000000000000000000000001
    所以char a的输出值应该是 - 1;
    同理,因为char == signed char,char a 和 signed char b是一样的,所以 signed char b的输出值也应该是 -1;

    unsigned char c的变量值是 - 1;先用32位二进制位求出补码。
    原码:10000000000000000000000000000001
    反码:11111111111111111111111111111110
    补码:11111111111111111111111111111111
    但因为是存放char类型中,只能取8个bite位,从补码后面截断8个比特位,得到11111111
    重点在这里!!!因为c变量是unsigned char,不考虑符号位,全是数值位,在发生整形提升时,统统补0就好了。所以得到:
    补码:00000000000000000000000011111111
    符号位是0,说明是正数,所以反码,原码相同,输出的值应该是255.
    在这里插入图片描述

    1.4代码二理解:

    
    int main()
    {
    	char a = -128;
    	printf("%d\n", a);
    	printf("%u\n", a);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    先求出 -128的补码:
    原码:10000000000000000000000010000000
    反码:11111111111111111111111101111111
    补码:11111111111111111111111110000000
    因为是char 类型,只能存放后面8bite位,发生截断得到:10000000
    又因为是char类型,补的是符号位,得到:11111111111111111111111110000000。
    如果使用%d打印,符号位是1,是负数,补码需要转变成原码:最后的结果自然是 -128;
    如果使用%u(十进制的形式打印无符号整型整数)打印,%u没有符号位的概念,所有二进制位都是数值位则打印的就是:11111111111111111111111110000000
    这数字很大,你忍一下。打印的是:4294967168
    在这里插入图片描述

    1.5代码三理解:

    #include
    #include
    int main()
    {
    	unsigned int i;
    	for(i = 9; i >= 0; i--)
    	{
    		printf("%u\n", i);
    		Sleep(1000);//减慢打印时间
    		            //单位是毫秒
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    其实很容易看出代码死循环了,因为unigned int 是无符号整型恒大于等于0,跳不出for循环。当 i = -1时,原本要跳出了的,但因为是无符号类型,负1的补码是:
    11111111111111111111111111111111。所以打印的是一个很大的数。
    在这里插入图片描述

    1.6代码四理解:

    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    先看for的条件的条件,i从0递增至1000次
    ,再看a[ i ]的赋值。
    -1,-2,-3,-4,-5,-6,-7…-998,-999。
    但是前面我们说了,char的取值范围是-128 ~ 127。多出的部分进行下一次循环。
    所以应该是如图:
    在这里插入图片描述
    strlen函数测的是字符串长度,判断结束的条件是遇见’0’;所以strlen在遇到第256个数字0时,就结束了判断。所以最终的输出应该是255;
    在这里插入图片描述

    1.7代码五理解:

    #include 
    
    unsigned char i = 0;//0~255
    int main()
    {
    for (i = 0; i <= 255; i++)
           {
              	printf("hello world\n");
           }
    	return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    unsigned char 的取值范围是0 ~ 255
    所以是死循环了。

    在这里插入图片描述

    三. 大小端字节序介绍及判断

    什么大端小端?

    大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址 中;
    小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中。

    为什么有大端和小端:

    为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8 bit的char之外,还有16 bit的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 main()
    {
    	int a = 1;//整型存放四个字节 00 00 00 01
    	          //用字符p来接收整型的第一个字节
    	char* p = (char*)&a;//a强制类型转换后取得一个字节的内容
    	if (*p == 1)        //用1 或 0判断段大小端存放
    		printf("小端\n");
    	else
    		printf("大端\n");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述
    我用的时VS2019,是小端字节序存储。
    在这里插入图片描述

    小编愚钝,如果有错误的地方请在评论区批评指出,看官走的时候给我个赞赞支持一下呗,谢谢。
    在这里插入图片描述

  • 相关阅读:
    Web 开发中 route 和 router 有什么区别?
    爬虫技术对携程网旅游景点和酒店信息的数据挖掘和分析应用
    vim的使用快捷键之删除、复制、粘贴
    第十九课,编写并调用自定义函数
    基础课4——客服中心管理者面临的挑战
    黔院长 | 不忘初心在逆境中前行!
    JWFD开源工作流矩阵引擎-遍历算法第二次修正代码
    Redis安装与配置、centos虚拟机上配置自启动redis服务
    如何构建 Protocol Buffers(protobuf)并解决常见问题
    bahir-flink
  • 原文地址:https://blog.csdn.net/LHY537200/article/details/130909236