一个普通技术宅的点点滴滴

0%

学习总结第三十三天——二进制的表示

我们都知道,在计算机中都是使用二进制来表示整数,但是很少有人深入理解计算机具体是怎么去表示的,认为其不重要,然而事实恰恰相反,许多知名的漏洞利用的都是人们不熟悉或者疏忽了计算机里具体执行的运算。所以对于了解二进制数是怎么存储在计算机里是很有必要的。

一个n位的十进制无符号数,其每个位可以看作一个n维向量中的带有权重的维,而其权重也与位置相关,以1234为例,4的权重为100,3的权重为101,2的权重为102,而1的权重为103,其数值即为各维的长度乘以该维的权重之和,即1×103+2*102+3*101+4*100=1234。

二进制无符号数同理,其每一位的权重即为2n,以1010(2)为例,其可看作一个四维向量,各维的权重从左到右依次是23,22,21,2^0。在计算机中表示二进制数时,是以字节为单位存储的。其有两种存储方式,大端法和小端法,区别在于每个数存储方式是从高地址位还是低地址位存储。如一个数为0x1234,有两个字节,如果在一个16位的机器上,分配两个内存地址分别为0x00和0x01的字节存储,在采用大端法时,内存地址0x00中存储的是0x12,0x11中存储的是0x34;而小端法相反,在0x00中存储的是0x34,0x11中存储的是0x12。这就造成了在不同的机器中,同一段代码可能会有不同的结果。下面这段代码就利用这一点,检测了运行的机器采用的是大端法还是小端法。

#include <stdio.h>

typedef unsigned char* byte_pointer;

int islittle_indian()
{
  int i=0xFF;
  byte_pointer ptr = (byte_pointer)&i;
  if ((*ptr) == 0xFF)
  {
    return 1;
  }
  if ((*ptr) == 0x00)
  {
    return 0;
  }
  else
  {
    return -1;
  }
}

int main(int argc, char const *argv[])
{
  printf("%d\n",islittle_indian());
  return 0;
}

这段代码在采用大端法的机器上运行的时候,输出0,在小端法的机器上运行的时候,便会输出1。尽管现在我们身边采用大端法的机器不多了,但是仍然需要了解。

说了这么多,表示范围仍然在无符号数内,而要扩展到有符号数,就不得不提目前大部分计算机都在采用的有符号数表示方法——补码,许多国内教科书介绍补码时,总是提到将最高位表示符号即原码,原码取反加1就是补码,但是完全没说补码的定义。其实如果了解到无符号数可以看作n维向量这一点,就很好理解补码了。

补码就是将最高位的权重变为负值的表示方式

如1010(2),如果我们按照无符号数的解读方式去乘相关的权重的话,会得到123+1*21=10。而如果我们当成补码去解读的话,就是1(-23)+2*21 = -6,这也得以说明为什么补码中负数的表示范围要比正数大1。补码有很多优点,如进行减法的运算和加法一样,在正数范围内与无符号数表示方法一样等等。虽然补码很方便,但是也要注意与无符号数混用时,经常会产生难以发现且严重的bug。

写了这么多,全面了解计算机系统对于写出健壮程序的重要性可见一斑,今后也应当多留心这方面的东西,才能更好地理解自己写出的程序。