对程序员而言,CPU是什么?
- CPU内部由寄存器、控制器、运算器、时钟四个主要部分组成
- 寄存器用来暂存指令、数据等处理对象,可以看作是一种特殊的内存
- 控制器控制器负责把内存上的指令、数据等读入寄存器,并根据指令执行结果控制计算机
- 运算器负责运算从内存读入寄存器的数据
- 时钟负责发出CPU开始计时的信号
- CPU可以执行的指令种类
- 程序可以写得非常复杂,但是CPU本身能做的事情非常简单
- 数据送达指令
- 寄存器与内存、内存与内存、寄存器与与外围设备间的数据读写操作
- 运算指令
- 累加寄存器执行算术运算、逻辑运算、比较运算与位移运算
- 跳转指令
- 实现条件分支、循环、强制跳转等
- call/return指令
- 函数调用/返回调用前的地址
CPU是寄存器的集合体
- 程序是指令和数据的集合
- 程序启动后,操作系统将程序从磁盘复制到主内存
- 时钟信号发生器发出时钟信号
- 根据时钟信号,控制器从主内存中读取指令和数据,加载进寄存器
- 运算器对寄存器中的指令进行解释和运行,对数据进行运算
- 控制器根据运算结果控制计算机
- 使用高级语言编写的程序,最后都会编译转化为机器语言,通过CPU内部的寄存器来处理。
- 对程序员而言,CPU是寄存器的集合体
寄存器分类
- 累加寄存器(accumulator register)
- 存储执运算时的数据和运算后的数据
- 标志寄存器(flag register)
- 存储运算处理后的CPU状态(运算结果)
- 跳转指令可以向程序计数器指定地址
- 是否执行跳转则需要根据标志寄存器中的值来判断
- 标志寄存器有3位分别存储运算结果是否为正、是否为0、是否为负
- 程序计数器(program counter)
- 存储下一条指令所在的内存地址,* 决定程序的执行流程
- 程序运行过程中,这里存储的地址值自增,就是程序的顺序执行
- 程序运行过程中,向这里指定存储的地址值,就实现了跳转。
- 分支是根据条件执行指定地址中存储的指令
- 循环是重复执行同一地址中存储的指令
- 基址寄存器(base register)
- 存储数据内存的起始地址
- 与变址寄存器一起,可以在物理上实现数组
- 变址寄存器(index register)
- 存储基址寄存器的想的相对地址
- 就是数组索引的物理实现
- 与基址寄存器一起,可以在物理上实现数组
- 通用寄存器(general purpose register)
- 存储任意数据
- 指令寄存器(instruction register)
- 存储指令。CPU内部使用,程序员无法通过程序对该寄存器进行操作。
- 栈寄存器(stack register)
- 存储栈区域的起始地址。实现函数(方法)的基础。
- 累加寄存器(accumulator register)
- 比较的实现机制
- 程序中对a和b进行大小比较,本质上就是CPU内部在做a-b的减法运算
- 无论运算结果为正、为0、为负,都会存储在标志寄存器中
- 结果为正,说明a比b大;结果为0,说明两者相等;结果为负,说明a比b小
- 函数调用的实现机制
- 函数调用也是通过把程序即使器中的值设置为存储函数的地址来实现的
- 与分支、循环不同之处在于,函数的调用需要在完成函数内部的处理后,处理流程再返回函数的调用点。因此仅仅通过跳转指令无法实现函数调用。(因为函数中的各条指令再内存中是分散存储的,无法判断什么时候该跳转回来)
- 高级编程语言进行编译后,函数调用处理会转换为call指令,函数结束处理会转换成return指
- 使用call指令实现函数调用,将函数入口地址设置到程序计数器之前,call指令会将函数被调用后要执行的指令统一存储(原本是在内存中分散存储的)到名为”栈”的主内存中(方法入栈)
- 函数处理完毕后,再通过函数的出口来执行return指令,将保存在栈中的地址(函数调用点的下一条指令的地址)设置到程序计数器中令
- 数组和索引的实现机制
- 数组本质上就是在内存中划分出的一段连续的存储区域
- 这段连续区域的划分,通过基址寄存器+变址寄存器来实现
- 基址寄存器存储数组的起始内存地址
- 变址寄存器存储数组地址相对起始地址的变化范围。变址内存器中存储的值就相当于高级编程语言中数组的索引。
- 如查询10000地址~10FFF时,可以将10000存入基址寄存器,使变址寄存器的值在0000~0FFF的范围内变化即可。
数据是用二进制表示的
- 移位运算
- 位移运算指的是将二进制数值的各个数位进行左右移位(shift)的运算。
- 有左移(向高位方向,使用 << 运算符表示)和右移(向低位方向,使用 >> 运算符表示)两种
- 移位后空出来的位置要进行补位,溢出的部分则自动忽略。
- 左移空出来的低位,使用0进行补位
- 右移空出的来高位,使用什么补位要视符号位而定(正数补0,负数补1)
- 十进制数左移后会变成原来的10倍、100倍、1000倍…右移会变成原来的1/10、1/100、1/1000…二进制的移位同理,左移变成原来的2倍、4倍、8倍…右移变成原来的1/2、1/4、1/8…
- 补数
- 二进制表示负数时,会把最高位作为符号来使用,因此最高位被称为”符号位“,符号位为0表示正数,符号位为1表示负数
- 计算机在做减法运算时,内部实际上是在做加法运算
- 补数就是用正数来表示负数,通过将二进制表示的各个数位的数值全部取反,然后将结果加1,获得补数
- 将二进制数的值取反之后加1的结果,和原来的值相加,结果为零。求补数的操作,等同于十进制数*-1
- 逻辑移位
- 当二进制数的值表示图形模式而非数值模式时,移位后在空出来的位上补0(无论左移右移都补0)。类似于霓虹灯左右滚动的效果。这样的移位被称为逻辑移位
- 符号扩充
- 以8位二进制数为例,在保持值不变的情况下,将其转换成为16位和32位的二进制数。
- 实际相当于右移,按照符号位的值,将左边的空位填充上即可。
- 逻辑运算
- 将二进制表示的信息作为四则运算的数值来处理,就是算数
- 算数运算指加减乘除四则运算
- 将二进制表示的信息单纯地看作0和1的罗列来处理,就是逻辑
- 逻辑运算指对二进制数各数字位的0和1分别进行处理的运算(把0看作false,1看作true会更好理解)
- 逻辑非(NOT) 取反操作
- 逻辑与(AND) 两个值都是1(true)时,结果为1(true),否则结果为0(false)
- 逻辑或(OR) 两个值有有一方是1(true)时,结果是1(true),双方都为0(false)时结果为0(false)
- 逻辑异或(XOR) 排斥相同的数值,两个值相同时结果为0(false),两个值不同时结果为1(true)
用二进制表示小数
将二进制表示的小数1011.0011转换为十进制表示: 1*2^3 + 0*2^2 + 1*2^1 + 1*2^0 + 0*2^(-1) +0*2^(-2) + 1*2^(-3) + 1*2^(-4) = 11.1875
有一些十进制数无法用二进制表示,比如十进制数0.1,就像十进制小数无法正确表示1/3一样,强行转换会变成无限循环小数,计算机的功能是有限的,无法直接处理循环小数。
- 使用二进制表示小数时,以0.0000~0.1111为例,转换成十进制只能表示成0.5、0.25、0.125、0.0625这四个二进制数小数点后面的位权组合而成的小数。像十进制的0.1,无论小数点后增加多位,2的-OO次幂怎么相加都无法得到。
浮点数
- 计算机以浮点数的形式处理小数
- 浮点数是指采用符号、尾数、基数和指数四部分来表示的小数
- 浮点数举例:0.12345 * 10^3 (同一个数,根据指数的不同,小数点的位置会变化)
- 对应的定点数: 123.45 (同一个数,小数点的位置是固定的)
- 由于计算机内部只能处理二进制数,因此基数固定为2。实际使用时不考虑基数,只用符号、尾数、指数三部分即可表示浮点数
- 符号部分
- 使用一个数据位来表示符号,0表示”0或正”,1表示负
- 尾数部分
- 将小数点前面的值,固定为1的正则表达式
- 按照特定的规则来表示数据的形式即是正则表达式
- 具体就是将二进制表示的小数进行数次逻辑移位,使整数部分的第一位(从右往左)变成1,第二位及之后都变为0。并且第一位的1在实际的数据中并不保持,空出来一位可以表示更大范围的数据。
- 指数部分
- 为了表示负数时不使用符号位,指数部分使用EXCESS系统表现
- EXCESS系统表现:通过将指数部分表示范围的中间值设置为0,使得负数不需要用符号来表示
- eg:指数部分是8位单精度浮点数时,最大值11111111=255的1/2,即01111111=127(舍弃小数部分)表示0。大于01111111的部分表示正数,小于01111111的部分表示负数
- 符号部分
- 单精度浮点数类型用32位表示全体小数
- 双精度浮点数类型用64位表示全体小数
- 计算机进行小数计算出错的原因
- 使用浮点数来处理小数
- “位溢出”造成计算错误
- 如果避免出错
- 无视错误,计算精度达到要求即可
- 把小数转换成整数来计算
- 二进制与十六进制
- 位数太多时,使用二进制展示数据效率不高,转换为十六进制表示更简洁。
- C语言程序中,在数值开头加上0x就可以表示十六进制
- 二进制数的4位,即相当于十六进制中的1位(2^4=16)