在日常代码的编写过程中,数据的创建和存储与我们代码的顺利运行和实现密不可分。而了解数据是如何存储、以什么形式进行存储对我们今后写出优质的代码起着举足轻重的作用,也是我们增强代码理解能力的必备知识储备。
或多或少接触或写过代码的人都知道,一个变量的创建是要在内存中开辟空间的,而空间的大小是根据不同的类型而决定的,字符char占1个字节,整形int一般占用4个字节,短整型short占两个字节,单精度浮点型float占四个字节,双精度浮点型double占8个字节。
本章主要围绕 整形存储 以及 大小端存储 来赘述。
一、整形在内存中的存储:原码、反码、补码
int a=10;
int b=-10;
此时我们创建了两个int(整形)变量,a和b。而整形在存储时会向内存申请4个字节大小的空间。而整形在存储时,存储的并不是我们直接赋予它的十进制数字,而是将其转换成二进制后进行存储,也就是存储时存储的是这个数的二进制位。
而一个Byte(字节)是由8个bit(比特位)组成的,而一个整形由4个字节组成。所以换算一下:1 int = 4 Byte= 32 bit。所以a也就是10的二进制位就如下所示。
0000 0000 0000 0000 0000 0000 0000 1010
而整形数据的二进制表示形式有3种:原码、反码、补码。而上面的二进制表示形式就是a的原码,也就是直接将10转化成二进制后的形式。
反码就是在原码基础上符号位不变,其他位按位取反。
补码就是在反码的基础上+1得到的。
而作为一个整数最后存到内存中的是它的补码。
三种表示方法均有符号位和数值位两部分,符号位也就是32位二进制表示形式中的第一位,符号位用‘0’表示正,用‘1’表示负,而数值位则是根据数据本身的值计算出来的。
整数分为正整数和负整数, 对于正整数来说,其原码反码补码都是一样的,拿int a举例。a作为一个正整数,原、反、补都是一样的。
0000 0000 0000 0000 0000 0000 0000 1010//原码
0000 0000 0000 0000 0000 0000 0000 1010//反码
0000 0000 0000 0000 0000 0000 0000 1010//补码
而拿int b举例,就需要按照上面的方法,转换出它的原、反、补。然后将补码存储到内存中去。
1000 0000 0000 0000 0000 0000 0000 1010//原码
1111 1111 1111 1111 1111 1111 1111 0101//反码
1111 1111 1111 1111 1111 1111 1111 0110//补码
为什么要采用补码来存储呢?
因为在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
而将两个整数相加减时,也是将两个数的补码直接进行计算。为了验证我们一个简单的例子来进行验证:
//拿-1+1来举例
//1为正整数所以原、反、补都相同
0000 0000 0000 0000 0000 0000 0000 0001//原、反、补
//-1的原、反、补不同
1000 0000 0000 0000 0000 0000 0000 0001//原码
1111 1111 1111 1111 1111 1111 1111 1110//反码
1111 1111 1111 1111 1111 1111 1111 1110//补码
如果直接将它们的原码相加:
0000 0000 0000 0000 0000 0000 0000 0001// a
1000 0000 0000 0000 0000 0000 0000 0001// b
1000 0000 0000 0000 0000 0000 0000 0002//相加之后
根据结果可以看到,直接将原码相加得出的值为-2。很明显-1+1不可能等于-2.而通过将它们的补码加起来就可以得到正确结果。
0000 0000 0000 0000 0000 0000 0000 0001 //a
1111 1111 1111 1111 1111 1111 1111 1110 //b
1 0000 0000 0000 0000 0000 0000 0000 0000//
加起来后第一位为1,但此时一共有33位,但内存中只能存储32位,多出来的第一位自动丢弃,丢弃后内存中就为0,0的原、反、补也都为0。
二、大端存储和小端存储
在查看内存时,我们经常看到下图所示的情况
拿创建的int n=10举例,10的二进制位在内存中占32个比特位,0000 0000 0000 0000 0000 0000 0000 1010,但二进制位过于长,所以在我们一般在使用VS编译器的内存窗口进行查看时,都会以16进制来显示,10转换成16进制就是a,而在进制转化中4个二进制位可以转换成一个十六进制位,一个十六进制位就是4个比特位,两个十六进制位就是一个字节,所以32个二进制位可以转换成8个16进制位,所以把10 转换成16进制位就是00 00 00 0a。
但我们在调试中的内存窗口发现,这个数据是倒着存放的,这就涉及到了数据在内存中的存储方式,即大小端字节序存储。它是以字节为单位,讨论存储顺序的。
大端字节序存储模式:是指数据的低位,保存在内存中的高地址中。而数据的高位,保存在内存的低地址中。
小端字节序存储模式:是指数据的低位,保存在内存的低地址中。而数据的高位,保存在内存的高地址中。
拿11 22 33 44来说,11的权重,也就是数值最大。或者换一种更易懂的说法,就比如123这个数,1是百位,2是十位,3是个位,1的权重明显是最大的。同样的 11 22 33 44 中11的权重最大,44的权重最小。所以对于11 22 33 44这个数来说11就是数据的最高位,44就是数据的最低位。
而内存的地址是从低位到高位的,一般通过编译器查看时,左边通常为低地址位,右边通常为高地址位,刚好与我们日常习惯相反。
所以如果按照大端存储,将数据高位存储在低地址那么11作为权重最高的位数就存放在左边,而44作为最低位就存放在右边。而按照小端存储,11作为最高位存放在高地址即右边,而44作为最低位则存放在左边。可能有点绕,大家可以结合上面大小端存储模式的定义在梳理一下。
而在通常的处理器一般采用小端存储,低地址一般都是在左边,高地址一般都是在右边,而存储方式一般则使用小端存储。