一. 信息在计算机里面的存储

无论是保存在硬盘里面作永久储存,还是保存在内存/寄存器里面作为计算的临时储存,信息在计算机里面需要一个表示方法,或者叫做保存方法.

首先,可以先来看一下c语言都有哪些信息的类型,或者说我们编程时候都用到哪些信息类型:

  • 布尔型,表示是与否
  • 整型,表示整数
  • 浮点型,表示小数
  • 字符串,表示一系列字符的集合
  • 数组,一系列上述类型的集合
  • 对象,一系列上述类型的索引集合

其中,布尔型可以用简单的 整型0&整型1 表示,就不单独说明.

由于一些基本的概念,比如字符串用ASCII码/UTF-8编码表示,整型根据不同的长度有,是否有符号,会有不同的范围,下面只挑选不同信息类型(或者叫数据类型)的值得提一下的点作说明.

  • 整型:

整型无符号形式不用多说,而有符号的表示形式除了简单的使用最高位作为符号位(1000 0001 表示-1),还有两种表现形式:补码表示反码表示.

实际上,补码相对于原码和反码,对于计算机的计算是更优化的.大多数现代计算机都用补码表示有符号数.

补码中, 如 1000 0001,将最高有效位解释成负权,而后, 所以 1000 1111 = **-**2^7 **+**2^3 **+**2^2 **+**2^1 **+**2^0 = -113

而反码就与补码类似,不过补码的负权为 - 2 ^(w-1), 反码的负权为 -2^(w-1) - 1, 其中w为整型长度

补码相比反码和原码(即最高位表示符号位的形式)的差别在于:

  1. 补码的0000 0000表示0,没有-0表示,而反码和原码都存在一个-0表示 (反码为1111 1111, 原码为 1000 0000)

  2. 补码的加减乘除运算,都等价于将补码转换为无符号数的加减乘除,再转换回补码,而反码和原码都要先作相应的转换.(至于为什么反码和补码不能直接运算,可以参考这个文章: 原码, 反码, 补码 详解)

  • 浮点型

书中介绍了一种浮点型表示的标准: IEEE浮点表示.

IEEE浮点表示用是三部分来表示一个浮点数: 符号位,尾数,阶码

其中,符号位s直接编码正负; 尾数和阶码编码浮点数的两部分值得: 指数E和小数M…浮点数V可以表示为: V = (-1)^s * M * 2^E

指数M和尾数E的计算如图…

2. 使用存储的信息进行运算

在计算机中,常用的运算有加,减,乘,除,取模,取反,位移.

其中加,减,取反,位移,都可以直接在信息的位表示直接操作,而乘,除,取模比较特殊.

  • 乘法

可以回想一下笔算乘法的过程,需要将乘数的各个位与被乘数相乘,再作相加.其中还涉及进位操作.如果计算机计算单元使用乘法的定义来进行乘法运算,可以想象需要很多步骤,而乘法作为基础的运算,太过复杂的运算显然是不能接受的.

于是人们利用了在计算机中,加法和位移是计算速度较快的特性(一次计算只需一个时钟周期),将乘法优化为被乘数乘以2的n次幂,再加上一个数,如:

7 * 25
= (7 * 16) + (7 * 8) + (7 * 1)
= (7 * 2^4) + (7 * 2^3) + 7

如此, 7 * 25 被优化至5个时钟周期(位移2 + 加法3)即可完成

3. 机器机语言

为什么需要计算机语言?

逻辑计算,其实可以看作是对于计算机里面数据的访问(读)和修改(写).计算机语言可以看做是读写这些数据的命令.汇编语言就是这种命令.而像C,php这类高级语言..只是汇编的一些封装.当高级语言需要执行时,并通过编译/虚拟机的形式..还原回汇编语言去对数据进行操作.

看计算机语言如何在计算机里执行,可以分拆开一个一个汇编命令的执行过程

  • 命令 从读写两个方面去将汇编命令分类,有:

  • 读: 访存/访寄存器mov lea:读取内存/寄存器地址

  • 写: 自增inc/减dec; 取负neg/补not; 加add减sub有符号乘imul无符号乘mul; 有符号除idiv无符号除div 异或xor或or与and; 左移shl/sal; 逻辑右移shr 算数右移sar

  • 压栈pushq 出栈popq

  • 跳转 为了实现代码的复用和逻辑跳转,汇编引入一种控制命令:

  • 返回ret

  • 调用代码段 call

  • 无条件跳转jmp 相同时跳转je/不相同时跳转jne 负数是跳转js/非负数时跳转jns

  • 有符号大于jg/大于大于jge/小于jl/小于等于jle 跳转

  • 无符号大于ja/大于大于jae/小于jb/小于等于jbe 跳转

这里看到,除了无条件跳转,跳转控制需要先判断两个数是否相同,或前者是否大于后者,这里引入了一种控制码,标记操作数的一些状态,每次计算命令结束后,都会根据结果设置控制码.

控制码有:CF进位标志; ZF零标志; SF负数标志; OF溢出标志

举个例子,if (1 > 2) testFunction(); 这一段C代码:

  • 计算机会先将1 2 这两个即使数放进寄存器mov 1 %rdi mov 2 %rsi
  • 然后用1减去2,执行sub %rdi %rsi; 计算后,因为1-2 = -1, 所以计算后,设置ZF=0, SF=1, CF=0, OF=0
  • 调用jg 0x000AF (假设这是testFunction的代码地址), jg命令发现ZF=0, SF=1, 判断前者减去后者肯定为负数,即前者小于后者,即不会进行跳转

4. 处理器体系

5. 优化程序性能