SS, SP, BP 三个寄存器
最后更新于:2022-04-02 04:23:22
[TOC]
## SS, SP, BP 三个寄存器
SS
- 存放栈的段地址
SP
- 堆栈寄存器SP(stack pointer)存放栈的偏移地址
BP
- 基数指针寄存器BP(base pointer)是一个寄存器,它的用途有点特殊,是和堆栈指针SP联合使用的,作为SP校准使用的,只有在寻找堆栈里的数据和使用个别的寻址方式时候才能用到
- 如:**堆栈中压入了很多数据或者地址,你肯定想通过SP来访问这些数据或者地址,但SP是要指向栈顶的,是不能随便乱改的,这时候你就需要使用BP,把SP的值传递给BP,通过BP来寻找堆栈里数据或者地址**
Win32汇编示例
```
bp为基址寄存器,一般在函数中用来保存进入函数时的sp的栈顶基址
每次子函数调用时,系统在开始时都会保存这个两个指针并在函数结束时恢复sp和bp的值。像下面这样:
在函数进入时:
push bp // 保存bp指针
mov bp,sp // 将sp指针传给bp,此时bp指向sp的基地址。
// 这个时候,如果该函数有参数,则[bp + 2*4]则是该子函数的第一个参数,[bp+3*4]则是该子函数的 第二个参数,以此类推,有多少个参数则[bp+(n-1)*4]。
.....
函数结束时:
mov sp,bp // 将原sp指针传回给sp
pop bp // 恢复原bp的值。
ret // 退出子函数
```
';
控制流 指令集
最后更新于:2022-04-02 04:23:20
[TOC]
## 控制流
### if else系列
```
b target # unconditional branch to program label target
beq $t0,$t1,target # branch to target if $t0 = $t1
blt $t0,$t1,target # branch to target if $t0 < $t1
ble $t0,$t1,target # branch to target if $t0 <= $t1
bgt $t0,$t1,target # branch to target if $t0 > $t1
bge $t0,$t1,target # branch to target if $t0 >= $t1
bne $t0,$t1,target # branch to target if $t0 <> $t1
```
### 跳转(while, for, goto系列)
```
j target # 看到就跳, 不用考虑任何条件
jr $t3 # 类似相对寻址,跳到该寄存器给出的地址处
```
### 子程序调用
```
jal sub_label #将当前的程序计数器保存到 $ra 中
```
- 如果说调用的子程序中有调用了其他子程序,如此往复, 则返回地址的标记就用 栈(stack) 来存储, 毕竟 $ra 只有一个
';
算术指令集
最后更新于:2022-04-02 04:23:17
[TOC]
## 算术指令集
* 最多3个操作数
* 再说一遍,在这里,操作数只能是寄存器,绝对不允许出现地址
* 所有指令统一是32位 = 4 \* 8 bit = 4bytes = 1 word
```
add $t0,$t1,$t2 # $t0 = $t1 + $t2; add as signed (2's complement) integers
sub $t2,$t3,$t4 # $t2 = $t3 Ð $t4
addi $t2,$t3, 5 # $t2 = $t3 + 5; "add immediate" (no sub immediate)
addu $t1,$t6,$t7 # $t1 = $t6 + $t7; add as unsigned integers
subu $t1,$t6,$t7 # $t1 = $t6 + $t7; subtract as unsigned integers
mult $t3,$t4 # multiply 32-bit quantities in $t3 and $t4, and store 64-bit
# result in special registers Lo and Hi: (Hi,Lo) = $t3 * $t4
# 运算结果存储在hi,lo(hi高位数据, lo地位数据)
div $t5,$t6 # Lo = $t5 / $t6 (integer quotient)
# Hi = $t5 mod $t6 (remainder)
# 商数存放在 lo, 余数存放在 hi
mfhi $t0 # move quantity in special register Hi to $t0: $t0 = Hi
# 不能直接获取 hi 或 lo中的值, 需要mfhi, mflo指令传值给寄存器
mflo $t1 # move quantity in special register Lo to $t1: $t1 = Lo
# used to get at result of product or quotient
move $t2,$t3 # $t2 = $t3
```
';
加载/保存 指令集
最后更新于:2022-04-02 04:23:14
[TOC]
## 加载/保存(读取/写入)
* 如果要访问内存,不好意思,你只能用**load**或者**store**指令
* 其他的只能都一律是寄存器操作
```
//从内存中 复制 RAM_source 的内容到 对应的寄存器中(lw中的'w'意为'word',即该数据大小为4个字节)
lw register_destination, RAM_source
//同上, lb 意为 load byte
lb register_destination, RAM_source
//将指定寄存器中的数据 写入 到指定的内存中
sw register_source, RAM_destination
// 同上, b 为 byte
sb register_source, RAM_destination
//这里的 li 意为 load immediate
li register_destination, value
```
## 立即与间接寻址
直接给地址
```
// 将var1的内存地址复制到寄存器$t0中
la $t0, var1
```
地址是寄存器的内容(可以理解为指针)
```
// 将包含在$t0中的内存地址的字加载到$t2中
lw $t2, ($t0)
// 将寄存器$t2中的字存储到内存中$t0中包含的地址
sw $t2, ($t0)
```
偏移量
```
// 将内存地址($t0+4)的字加载到寄存器$t2中
lw $t2, 4($t0)
// 将寄存器$t2中的字存储到内存中的地址($t0 - 12)
sw $t2, -12($t0)
```
* 要用到偏移量的寻址,基本上使用最多的场景无非两种:数组,栈
实例
```
.data
array1: .space 12 # declare 12 bytes of storage to hold array of 3 integers
# 定义一个 12字节 长度的数组 array1, 容纳 3个整型
.text
__start: la $t0, array1 # load base address of array into register $t0
# 让 $t0 = 数组首地址
li $t1, 5 # $t1 = 5 ("load immediate")
sw $t1, ($t0) # first array element set to 5; indirect addressing
# 对于 数组第一个元素赋值 array[0] = $1 = 5
li $t1, 13 # $t1 = 13
sw $t1, 4($t0) # second array element set to 13
# 对于 数组第二个元素赋值 array[1] = $1 = 13
# (该数组中每个元素地址相距长度就是自身数据类型长度,即4字节, 所以对于array+4就是array[1])
li $t1, -7 # $t1 = -7
sw $t1, 8($t0) # third array element set to -7
# 同上, array+8 = (address[array[0])+4)+ 4 = address(array[1]) + 4 = address(array[2])
done
```
';
指令集
最后更新于:2022-04-02 04:23:11
[加载/保存 指令集](%E6%8C%87%E4%BB%A4%E9%9B%86/%E5%8A%A0%E8%BD%BD-%E4%BF%9D%E5%AD%98%E6%8C%87%E4%BB%A4%E9%9B%86.md)
[算术指令集](%E6%8C%87%E4%BB%A4%E9%9B%86/%E7%AE%97%E6%9C%AF%E6%8C%87%E4%BB%A4%E9%9B%86.md)
[控制流 指令集](%E6%8C%87%E4%BB%A4%E9%9B%86/%E6%8E%A7%E5%88%B6%E6%B5%81.md)
';
MIPS
最后更新于:2022-04-02 04:23:09
[TOC]
## 数据类型
* 所有MIPS指令都是32位长的
* 各单位:1字节=8位,半字长=2个字节,1字长=4个字节
* 一个字符空间=1个字节
* 一个整型=一个字长=4个字节
* 单个字符用单引号,例如:'b'
* 字符串用双引号,例如:"A string"
## 寄存器
|寄存器编号 |寄存器名| 寄存器用途|
|---|---|---|
|0| zero| 永远返回零|
|1|$at|汇编保留寄存器(不可做其他用途)|
|2-3|$v0 - $v1|(Value简写)存储表达式或者是函数的返回值|
|4-7|$a0 - $a3|(Argument简写)存储子程序的前4个参数,在子程序调用过程中释放|
|8-15|$t0 - $t7|(Temp简写)临时变量,同上调用时不保存|
|16-23|$s0 - $s7|(Saved or Static简写?)静态变量?调用时保存|
|24-25|$t8 - $t9|(Temp简写)算是前面$0~$7的一个继续,属性同$t0~$t7|
|26-27|$k0 - $k1|(breaK off简写?)中断函数返回值,不可做其他用途|
| 28|$gp |(Global Pointer简写)指向64k(2^16)大小的静态数据块的中间地址(字面上好像就是这个意思,块的中间)|
| 29|$sp |(Stack Pointer简写)栈指针,指向的是栈顶|
| 30| $s8/$fp |(Saved/Frame Pointer简写)帧指针|
| 31|$ra| 返回地址,目测也是不可做其他用途|
## 程序结构
* 本质其实就只是数据声明+普通文本+程序编码(文件后缀为.s,或者.asm也行)
* 数据声明在代码段之后(其实在其之前也没啥问题,也更符合高级程序设计的习惯)
### 数据声明
- 数据段以 .data为开始标志
- 声明变量后,即在主存中分配空间。
### 代码
* 代码段以 **.text**为开始标志
* 其实就是各项指令操作
* 程序入口为**main:**标志(这个都一样啦)
* 程序结束标志(详见下文)
### 注释
```
# Comment giving name of program and description of function
# Template.s
#Bare-bones outline of MIPS assembly language program
.data # variable declarations follow this line
# 数据变量声明
# ...
.text # instructions follow this line
# 代码段部分
main: # indicates start of code (first instruction to execute)
# 主程序
# ...
# End of program, leave a blank line afterwards to make SPIM happy
# 程序结束,留下一个空行之后
```
### 数据声明
```
name: storage_type value(s)
变量名:(冒号别少了) 数据类型 变量值
```
* 通常给变量赋一个初始值;对于**.space**,需要指明需要多少大小空间(bytes)
### 实例
```
example:
.data
var1: .word 23 # declare storage for var1; initial value is 23
# 先声明一个 word 型的变量 var1 = 3;
.text
__start:
lw $t0, var1 # load contents of RAM location into register $t0: $t0 = var1
# 令寄存器 $t0 = var1 = 3;
li $t1, 5 # $t1 = 5 ("load immediate")
# 令寄存器 $t1 = 5;
sw $t1, var1 # store contents of register $t1 into RAM: var1 = $t1
# 将var1的值修改为$t1中的值: var1 = $t1 = 5;
done
```
';
汇编
最后更新于:2022-04-02 04:23:06
[MIPS](%E6%B1%87%E7%BC%96/MIPS.md)
';
零钱问题
最后更新于:2022-04-02 04:23:04
[TOC]
## 问题
1美元换 50美分,24美分,10美分,5美分,1美分,有几种方式
## 思考
将总数为a的现金换成n种硬币的不同方式的数目等于
1. 将现金数a换成除第一种硬币之外的所有其他硬币的不同方式数目,加上
2. 将现金数a-d换成所有种类的硬币的不同方式数目,其中的d是第一种硬币的币值
将某个给定现金数的换零钱方式的问题,递归地归**约为**对更少现金数或者更少种类硬币的同一个问题。
- 如果a就是0,应该算作是有1种换零钱的方式。
- 如果a小于0,应该算作是有0种换零钱的方式
- 如果n是0,应该算作是有0种换零钱的方式。
```
(define (count-change amount)
(cc amount 5))
(define (cc amount kinds-of-coins)
(cond ((= amount 0) 1)
((or (< amount 0) (= kinds-of-coins 0)) 0)
(else (+ (cc amount
(- kinds-of-coins 1))
(cc (- amount
(first-denomination kinds-of-coins))
kinds-of-coins)))))
(define (first-denomination kinds-of-coins)
(cond ((= kinds-of-coins 1) 1)
((= kinds-of-coins 2) 5)
((= kinds-of-coins 3) 10)
((= kinds-of-coins 4) 25)
((= kinds-of-coins 5) 50)))
;; -> 292
(count-change 100)
```
';
最大子序和
最后更新于:2022-04-02 04:23:01
[TOC]
## 最大子序和
示例 1:
```
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
```
示例 2:
```
输入:nums = [1]
输出:1
```
## 实现
一种简单理解实现
```
func maxSubArray(nums []int) int {
if len(nums) < 2 {
return nums[0]
}
max := nums[0]
lastMax := nums[0]
for i := 1; i < len(nums); i++ {
n := nums[i]
if tmp := n + max; tmp > n {
max = tmp
} else {
max = n
}
}
return lastMax
}
```
利用 nums[i] 保存 lastMax 的值
```
func maxSubArray(nums []int) int {
if len(nums) < 2 {
return nums[0]
}
max := nums[0]
for i := 1; i < len(nums); i++ {
if nums[i] < nums[i]+nums[i-1] {
nums[i] = nums[i] + nums[i-1]
}
if nums[i] > max {
max = nums[i]
}
}
return max
}
```
';
爬楼梯
最后更新于:2022-04-02 04:22:58
[TOC]
## 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
>找到关系式 dp[i] = dp[i-1] + dp[i-2] n层台阶有 dp[n]
```
func climbStairs(n int) int {
if n<=3{
return n
}
dp:=make([]int,n+1)
dp[1]=1
dp[2]=2
for i:=3;i<=n;i++ {
dp[i]=dp[i-1]+dp[i-2]
}
return dp[n]
}
```
';
动态规划
最后更新于:2022-04-02 04:22:56
[爬楼梯](%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/%E7%88%AC%E6%A5%BC%E6%A2%AF.md)
[最大子序和](%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%92%8C.md)
';
Sunday算法
最后更新于:2022-04-02 04:22:53
[TOC]
## 字符串匹配——Sunday算法
';
跳表
最后更新于:2022-04-02 04:22:51
[TOC]
## 概述
- 跳表(SkipList,全称跳跃表)是用于有序元素序列快速搜索查找的一个数据结构
- 实质就是一种可以进行二分查找的有序链表
';
KMP 算法
最后更新于:2022-04-02 04:22:48
[TOC]
## 场景
在一个字符串中是否包含另一个字符串
## 概述
- 一句话概括,KMP算法的核心是已经匹配的字符不会重复匹配
- KMP算法的核心,是一个被称为部分匹配表(Partial Match Table)的数组
### 图示
![](https://pic.leetcode-cn.com/023ce6af45af247b3c140ca0ebc0ac6d93b1fa081a1ae0fa9e1a83d790eabee0-file_1568963023074)
![](https://pic.leetcode-cn.com/8e02ee035c94ce3f8897c9f0a15e3b4c8e029a0b8b6e9ab0c3189528089c3742-file_1568963023090)
## 解析
假设现在文本串 S 匹配到 i 位置,模式串 P 匹配到 j 位置
* 如果 j = -1,或者当前字符匹配成功(即 S\[i\] == P\[j\]),都令 i++,j++,继续匹配下一个字符;
* 如果 j != -1,且当前字符匹配失败(即 S\[i\] != P\[j\]),则令 i 不变,j = next\[j\]。此举意味着失配时,模式串 P 相对于文本串 S 向右移动了 j - next \[j\] 位。
* 换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的 next 值,即**移动的实际位数为:j - next\[j\]**
* 如果 next [j] 等于 0 或 -1,则跳到模式串的开头字符,若 next [j] = k 且 k > 0,代表下次匹配跳到 j 之前的某个字符
思路
1.构造next数组(初始化->处理前后缀不相同的情况->处理前后缀相同的情况)
2.使用next数组来做匹配
golang 实现
```
func strStr(haystack string, needle string) int {
n:=len(haystack)
m:=len(needle)
if (m==0){
return 0
}
if (n0 && haystack[i]!=needle[q]{
q=next[q-1]
}
if ( haystack[i]==needle[q]){
q++
}
if (q==m){
return i+1-m;
}
}
return -1
}
func compute_next( pattern string) []int {
n:=len(pattern)
next:=make([]int,n)
k:=0
for q:=1;q0 && ( pattern[k]!=pattern[q]){
k=next[k-1];
}
if ( pattern[k]==pattern[q]){
k++;
}
next[q]=k;
}
return next
}
```
';
查找算法
最后更新于:2022-04-02 04:22:46
[KMP 算法](%E6%9F%A5%E6%89%BE%E7%AE%97%E6%B3%95/KMP%E7%AE%97%E6%B3%95.md)
[跳表](../%E8%B7%B3%E8%A1%A8.md)
[Sunday算法](%E6%9F%A5%E6%89%BE%E7%AE%97%E6%B3%95/Sunday%E7%AE%97%E6%B3%95.md)
';
算法
最后更新于:2022-04-02 04:22:43
[查找算法](%E7%AE%97%E6%B3%95/%E6%9F%A5%E6%89%BE%E7%AE%97%E6%B3%95.md)
[动态规划](%E7%AE%97%E6%B3%95/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92.md)
[零钱问题](%E7%AE%97%E6%B3%95/%E9%9B%B6%E9%92%B1%E9%97%AE%E9%A2%98.md)
';
将有序数组转换为二叉搜索树
最后更新于:2022-04-02 04:22:40
[TOC]
## 将有序数组转换为二叉搜索树
给你一个整数数组`nums`,其中元素已经按**升序**排列,请你将其转换为一棵**高度平衡**二叉搜索树。
![](https://assets.leetcode.com/uploads/2021/02/18/btree1.jpg)
解答
```
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func sortedArrayToBST(nums []int) *TreeNode {
return helper(nums,0,len(nums)-1)
}
func helper(nums []int,left,right int)*TreeNode {
if left>right {
return nil
}
mid:=(left+right)/2
root:=&TreeNode{Val:nums[mid]}
root.Left=helper(nums,left,mid-1)
root.Right=helper(nums,mid+1,right)
return root
}
```
';
对称二叉树
最后更新于:2022-04-02 04:22:38
[TOC]
## 对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
```
1
/ \
2 2
/ \ / \
3 4 4 3
```
解答
```
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func isSymmetric(root *TreeNode) bool {
if root==nil{
return true
}
return helper(root.Left,root.Right)
}
func helper(left *TreeNode,right *TreeNode)bool{
if left==nil && right==nil{
return true
} else if left==nil || right==nil || left.Val!=right.Val {
return false
}
return helper(left.Left,right.Right) && helper(left.Right,right.Left)
}
```
';
二叉树的最大深度
最后更新于:2022-04-02 04:22:35
[TOC]
## 二叉树的最大深度
```
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func maxDepth(root *TreeNode) int {
if root ==nil{
return 0
}
left:=maxDepth(root.Left)
right:=maxDepth(root.Right)
return max(left,right)+1
}
func max(a,b int)int{
if a>b{
return a
}
return b
}
```
';
示例
最后更新于:2022-04-02 04:22:32
[二叉树的最大深度](%E7%A4%BA%E4%BE%8B/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%A4%A7%E6%B7%B1%E5%BA%A6.md)
[对称二叉树](%E7%A4%BA%E4%BE%8B/%E5%AF%B9%E7%A7%B0%E4%BA%8C%E5%8F%89%E6%A0%91.md)
[将有序数组转换为二叉搜索树](%E7%A4%BA%E4%BE%8B/%E5%B0%86%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.md)
';