前言:在c语言中存在很多数据类型,它们在内存中的存储是存在不同的特性的,了解这个章节对深入了解c语言很有帮助。
1.整数在内存中的存储方式
整数在内存中有三种存储方式分别为:原码、反码、补码
如果数据的类型是有符号整数(signed)那数据在内存中的原码最高位为符号位,其中0表示正数1来表示负数,如果是无符号整数(unsigned int)时那4个字节的大小是数值位,这就是为什么无符号类型的整数比有符号整数可以表示更大的绝对值。
正整数的原码、补码、反码都是相同的,而负整数的原码、反码、补码各不相同下面是具体方式(来源于百度):
(1)原码:是一种计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数该位为0,负数该位为1(0有两种表示:+0和-0),其余位表示数值的大小。
(2)反码:是计算机编码系统中用于表示有符号数的二进制编码方法,与原码、补码共同构成三种基本表示法。其编码规则为:正数的反码与原码一致,负数的反码保持符号位不变,数值位按位取反
(3)补码:反码+1得到补码
在内存中不管是计算还是存储都是以补码的形式来进行的,因为补码和原码在计算机中可以互相转化不需要额外的电路我这里就不展开来讲了。
2.大小端字节序
这里我同个一串简单的代码来解释大小端字节序,我用的编译器是vs所以用vs(小端字节序)来演示:
int main() { int a = 0x11223344; return 0; }打开vs的调试:
这时我们会发现位数低的数组在低地址上存放,而高位数在高地址处存放。这里就引出了我们大小端字节序的概念:
在前面关于指针的文章中我曾经介绍过,内存分为一个个的内存单元,而一个内存单元能够存放的大小为一个字节,所以当我们要存储的数据大小超过一个字节(比如一个整形的大小为四个字节)时就会涉及到字节存储顺序的问题,所以根据存储顺序的不同分为两种模式
(1)大端字节序模式
大端字节序将高位字节存储在低地址处,低位字节存储在高地址处
(2)小端字节序模式
小端字节序则与大端字节序相反,低位字节存储在低地址处
2.1字节序的代码判断
通过编译器的调试模式我们可以轻而易举的判断大小端字节排序,我们同样也可以通过代码来进行判断:
int main() { int a = 1; if (*(char*)&a == 1) { printf("小端\n"); } else printf("大端\n"); return 0; }运行程序:
可以看到程序符号我们的预期成功判断了字节序为小端模式,原理也非常简单通过取出整形变量a的地址并强转为了char*类型,这样我们通过*来访问时就会访问第一个低地址字节的空间,因为整形变量a在内存中以二进制的形式存储时低位为1,这样我们就成功的证明了低地址存放的是低位上的数据,所以为小端字节序模式。
3.浮点数在内存中的存储
浮点数在内存中的存储不同于整数它有着单独的存储方式,这里我们同样通过一串代码来引出:
int main() { int n = 9; float* pFloat = (float*)&n;//int* printf("n的值为:%d\n", n); printf("*pFloat的值为:%f\n", *pFloat); *pFloat = 9.0; printf("num的值为:%d\n", n); printf("*pFloat的值为:%f\n", *pFloat); return 0; }运行结果为:
可以看到上第二低三行都输出了比较反直觉的数字,这又是什么原因呢?
这是因为浮点数在计算机中有专门的表示方法,有着一个统一的标准(IEEE754)
参考文献链接:维基百科介绍
浮点数在内存中的存储正是以这个公式为标准的,具体这么存下面我会慢慢介绍。我举一个例子来方便大家理解这个公式。
就以十进制的5.0来说,写成二进制的话就是101.0,在这个公式中表示为(-1)^0×1.01×2^2
因为该数为正数,所以符号位s = 0,而101.0在二进制中可以写成1.01×2^2 其中1.01是有效数字M而后面的2E(2^2)表示的是指数位
另外,这个标准还规定了浮点数在内存的存储方式:
对于32位的浮点数来说,最高位(sign)用一个位的空间来存放符号位来表示正负,接下来的八个位(exponent)来存放指数E,剩下的23位用于存储有效数字M。对于64位大小的浮点数也是一样的只不过用于存放指数位E的大小变为11位,M变为52位。这也是为什么64位双精度浮点数的存储精度更高的原因。
浮点数存储的过程细节:
前面也介绍过M的取值范围是1~2(不包含2),M在保存时只保存小数部分比如1.012就只会保存012,等需要读取时再加上一个1,这样做可以使得原本只能存放23位有效数字的M可以存放24位有效数字。
而指数E的存储则相对于M来说比较复杂,E是一个无符号的整数(unsigned int)但是我们的科学计数法是可以出现负数的,为了处理这个问题IEEE 754规定了当E为8位时需要加上127这个中间值来表示负数,而对于11位的E来说就需要加上1023
3.1浮点数取的过程
浮点数在取出的时候分为三种情况:
(1)E不全为0或者不全为1
这个一样的情况下,只需要把有效数字M加上一个1然后再减去E的中间值就可以了
(2)E全为1
但有效数字全为0时,会表示一个无穷大的数组正负取决于符号位
(3)E全为0
这个时候E减去中间值就是它的真实值了不用对M加1,用于表示接近0很小很小的数组,正负同样取决于符号位。
这个时候我们就可以解答一开始的那行代码了,再第二行中为什么9被打印出来就成了0.00……呢?
将9转化为二进制为0 00000000 00100000000000000000000
满足了E全部为0时的特殊情况,不再对M加1,指数部分再减去中间值所以此时的9时个很小很小非常接近与0的数字。
同样有了上面的知识就可以解答为什么9.0以整数的形式打印的时候会出现一个很大的数字了
9.0在内存中的存储方式为(−1)^0 ∗ (1.001) ∗ 2^3,写成以浮点数的存放形式就为
0 10000010 001 0000 0000 0000 0000 0000
因为正整数的原码、反码、补码都相同,所以当以整形的方式来打印时正是1091567616
完