5.14 汇编语言:仿写Switch选择结构
选择结构,也称为switch语句,是计算机编程中的一种控制结构,用于根据表达式的值选择不同的执行路径。它允许程序根据表达式的值来决定执行哪个代码块,从而实现多分支选择逻辑。switch语句由一个表达式、多个case标签以及对应的代码块组成。程序会将表达式的值与每个case标签进行匹配,一旦找到匹配的case标签,程序将执行对应的代码块,并继续执行该代码块之后的代码,直到遇到break语句或者switch语句结束。
11.25 仿写有序线性优化
在switch分支数小于4的情况下,编译器将采用模拟IF-ELSE分支的方式构建SWITCH结构,这样则无法发挥出SWITCH语句的优势,当分支数大于3并且case的判断值存在明显线性关系时,Switch语句的优化特性才可以被凸显出来。
该优化方式将每个case语句块的首地址预先保存在数组(地址表)中,并依据寻址时传入的下标(下标以0开头),在此数组中查询case语句块对应的首地址,取出首地址并跳转到指定分支上,并执行分支流程代码。
int main(int argc, char *argv[])
{
int index = 1;
switch (index)
{
case 1:
printf("index 1"); break;
case 2:
printf("index 2"); break;
case 3:
printf("index 3"); break;
case 6:
printf("index 6"); break;
case 7:
printf("index 7"); break;
default:
printf("default"); break;
}
return 0;
}
首先我们手动构建MemArray跳转地址表,通过offset伪指令取出分支内存地址,并将分支地址拷贝到MemArray跳转表中,因为分支语句下标从0开始,所以需要dec ecx减去1,在进入switch语句之前,判断输入的下标是否高于6,如果高于则直接跳出switch,否则执行jmp dword ptr ds:[ecx * 4 +MemArray]寻址并跳转到相应的分支上。
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
MemArray DWORD 0,0,0,0,0,0,0dh
InputNumber DWORD ?
.code
main PROC
; 寻找第2个分支语句
mov dword ptr ds:[InputNumber],2
; 填充跳转地址表
mov dword ptr ds:[MemArray],offset S0
mov dword ptr ds:[MemArray + 4],offset S1
mov dword ptr ds:[MemArray + 8],offset S2
mov dword ptr ds:[MemArray + 12],offset S3
mov dword ptr ds:[MemArray + 16],offset S4
mov dword ptr ds:[MemArray + 20],offset S5
mov dword ptr ds:[MemArray + 24],offset Send
; 判断下标是否高于6高于则结束switch
mov ecx,dword ptr ds:[InputNumber]
dec ecx ; Switch语句获取比例因子,需要减1
cmp ecx,6 ; 首先对比输入数据是否大于6
ja lop_end ; 大于则说明Switch分支无对应结构 (则直接跳转到结束)
jmp dword ptr ds:[ecx * 4 +MemArray] ; 否则直接寻址,找到对应的内存地址
S0:
mov eax,0
jmp lop_end
S1:
mov eax,1
jmp lop_end
S2:
mov eax,2
jmp lop_end
S3:
mov eax,3
jmp lop_end
S4:
mov eax,4
jmp lop_end
S5:
mov eax,5
jmp lop_end
Send:
mov eax,6
jmp lop_end
lop_end:
int 3
main ENDP
END main
11.26 仿写非线性索引优化
如果两个case值间隔较大,仍然使用switch的结尾地址或default地址代替地址表中缺少的case地址,这样则会造成极大的空间浪费,对于这种非线性结构,可采用制作索引表的方式进行优化,但此方式需要通过索引表来查询地址表,会多出一次查表的过程,因此效率上会有所下降。
索引表需要两张表:
case 语句块地址表:地址表中每一项保存一个case语句块首地址,有几个case语句块或default语句块,就保存几项,结束地址在地址表中只会保存一份。
case 语句块索引表:索引表中保存了地址表中的下标值,索引表最多可容纳256项,每项1字节,所以case值不可超过1字节,索引表也只能存储256项索引编号。
我们在上方C代码基础上稍加改动,如下分支结构4,5默认是不存在的,也就是当用户选择4或5时,默认会执行6号分支,如果单独为4,5创建一个4字节存储,分支偏差小还能接受,一旦分支偏差过大,则会占用大量内存空间,此时就需要使用索引表来优化空间占用。
int main(int argc, char *argv[])
{
int index = 5;
switch (index)
{
case 1:
printf("index 1"); break;
case 2:
printf("index 2"); break;
case 3:
printf("index 3"); break;
case 6:
printf("index 6"); break;
case 7:
printf("index 7"); break;
}
return 0;
}
这段C代码如果改成非线性优化则会呈现以下类型的汇编指令,与地址表差不多,索引表MemIndexTable中每一个字节对应一个地址表MemAddressTable的下标,如果该索引在4,5范围内,则会默认指向地址表MemAddressTable中同一块内存区域,这样即可解决内存资源浪费的问题。
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
MemAddressTable DWORD 0,0,0,0,0,0dh
MemIndexTable BYTE 0,0,0,0,0,0,0,0dh
InputNumber DWORD ?
.code
main PROC
; 寻找第5个分支语句 对应S3
mov dword ptr ds:[InputNumber],5
; 填充地址表
mov dword ptr ds:[MemAddressTable],offset S0
mov dword ptr ds:[MemAddressTable + 4],offset S1
mov dword ptr ds:[MemAddressTable + 8],offset S2
mov dword ptr ds:[MemAddressTable + 12],offset S3
mov dword ptr ds:[MemAddressTable + 16],offset S4
; 填充索引表
mov byte ptr ds:[MemIndexTable],0 ; 对应S0
mov byte ptr ds:[MemIndexTable + 1],1 ; 对应S1
mov byte ptr ds:[MemIndexTable + 2],2 ; 对应S2
mov byte ptr ds:[MemIndexTable + 3],3 ; 对应S3
mov byte ptr ds:[MemIndexTable + 4],3 ; 对应S3
mov byte ptr ds:[MemIndexTable + 5],3 ; 对应S3
mov byte ptr ds:[MemIndexTable + 6],4 ; 对应S4
; 得到索引表的基地址
lea edx,MemIndexTable
; 定位到第5个分支上
mov ecx,dword ptr ds:[InputNumber]
dec ecx
cmp ecx,7
ja lop_end
; 关键定位算法 索引表找下标,下标找函数地址
movzx eax,byte ptr ds:[ecx + edx] ; 从索引表找地址表下标
jmp dword ptr ds:[eax * 4 + MemAddressTable] ; 比例因子寻找函数地址
S0:
mov eax,0
jmp lop_end
S1:
mov eax,1
jmp lop_end
S2:
mov eax,2
jmp lop_end
S3:
mov eax,3
jmp lop_end
S4:
mov eax,4
jmp lop_end
lop_end:
int 3
main ENDP
END main
11.27 仿写平衡判定树优化
当最大case值与最小case值之差大于255时,则会采用判定树优化,将每个case值作为一个节点,从节点中找出中间值作为根节点,以此形成一颗平衡二叉树,以每个节点为判定值,大于和小于关系分别对应左子树和右子树,从而提高查询效率。
如果打开编译器体积优先,编译器尽量会以二叉判定树的方式来降低程序占用体积,如果无法使用前两种优化方式时,则需要将switch做成一棵树,首先编译C代码。
int main(int argc, char *argv[])
{
int index = 15;
switch (index)
{
case 2:
printf("index 2"); break;
case 3:
printf("index 3"); break;
case 8:
printf("index 8"); break;
case 10:
printf("index 10"); break;
case 35:
printf("index 35"); break;
case 37:
printf("index 37"); break;
case 666:
printf("index 666"); break;
}
return 0;
}
判定树,通过增加多条分支结构,从中位数10开始判断,大于走左子树或小于走右子树分支,直到遇到符合条件的分支为止,这段汇编代码编写时应格外注意次序,否则容易写乱套,不论如何本人还是按照编译器习惯将其转换为了对等汇编语句。
.386p
.model flat,stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
InputNumber DWORD ?
; 排列后 左子树大/右子树小 666 37 35 [10] 2 3 8
.code
main PROC
mov dword ptr ds:[InputNumber],40
; 先判断输入的数据是否大于最大分支
mov eax,666
cmp dword ptr ds:[InputNumber],eax
jg lop_end
; 取出中位数10作为第一个判定条件
cmp dword ptr ds:[InputNumber],10
jg left
je S10
cmp dword ptr ds:[InputNumber],8 ; 对比8
jle S8
cmp dword ptr ds:[InputNumber],7 ; 对比7
jle S37
cmp dword ptr ds:[InputNumber],2 ; 对比2
jle S2
jmp lop_end
left:
cmp dword ptr ds:[InputNumber],35 ; 对比35
jle S35
cmp dword ptr ds:[InputNumber],37 ; 对比37
jle S37
cmp dword ptr ds:[InputNumber],666 ; 对比666
jle S35
jmp lop_end
S2:
mov eax,2
jmp lop_end
S3:
mov eax,3
jmp lop_end
S8:
mov eax,8
jmp lop_end
S10:
mov eax,10
jmp lop_end
S35:
mov eax,35
jmp lop_end
S37:
mov eax,37
jmp lop_end
S666:
mov eax,666
jmp lop_end
lop_end:
int 3
main ENDP
END main
为了降低判定树的高度,在优化过程中,会检查代码是否满足if-else优化,有序线性优化,非线性索引优化,利用三种优化来降低树高度,谁的效率高就优先使用谁,如果三种优化都无法匹配才会使用判定树。
5.14 汇编语言:仿写Switch选择结构的更多相关文章
- 房上的猫:switch选择结构,与选择结构总结
switch选择结构: 一.定义: switch选择结构,可以方便地解决等值判断问题二.语法: switch(表达式){ case 常量1: //代码块1; break; c ...
- Java(4)switch选择结构
一.switch结构(开关语句)的语法 switch(表达式 ){--->类型为int.char case 常量1 :--->case 结构可以有多个 //语句块1 break;---& ...
- java中的结构--switch选择结构
if-switch 选择结构 switch结构可以更好的解决等值判断问题switch 选择结构的语法:switch (表达式){ case 常量 1: //代码块1: break; case 常量 2 ...
- 选择结构二switch选择结构
在上一章节我们讲解了if选择结构 本章我们学习 switch选择结构 还要知道if选择结构和switch结构的区别 为什么学习了if选择结构还要学习switch选择结构 以及 两种选择结构的运用 ...
- Java流程控制04——Switch选择结构
switch 多选择结构 switch case 语句判断一个变量与一系列值中某个值是否相等,每个支撑位一个分支. switch语句中的变量类型可以是: byte short int 或者 char ...
- Switch 选择结构
switch 选择器 一.语法 switch(变量名){ case 情况1: //代码块 break: case 情况1: //代码块 break: default(默认): //代码块 break: ...
- switch选择结构
switch( 表达式 )//表达式可以使用byte,short,int,char { case 值1: 逻辑语句: break;//跳出switch语句 case 值2: 逻辑语句; break; ...
- 选择结构的三角关系Switch、Case、Default!!!
选择结构的三角关系Switch.Case.Default!!! 今天我们学习选择结构进化章节——Switch结构,他与if有什么区别呢? 相同点: 都是用来处理多分支条件的结构 不同点: switch ...
- if和switch的选择结构
1. Java中的if选择结构,包括以下形式. *基本if选择结构:可以处理一单一或组合条件的情况. *if-else选择结构:可以处理简单的条件分支情况. *多重if选择结构:可以处理连续区间的条件 ...
- 程序选择结构if和switch的定义以及使用方法
什么是if选择结构 if选择结构是根据条件判断之后在做处理 基本的if选择结构的语法 if(条件){//条件为真则执行代码1,否则不执行 //代码块1 } if-else选择结构 为什么使 ...
随机推荐
- TypeError: this.libOptions.parse is not a function
安装完node.js运行项目后,报错: TypeError: this.libOptions.parse is not a function at ESLint8Plugin.<anonymou ...
- Java Kafka 消费积压监控
Java Kafka 消费积压监控 后端代码: Monitor.java代码: package com.suncreate.kafkaConsumerMonitor.service; import c ...
- [计数dp] 整数划分(模板题+计数dp+完全背包变种题)
计数类 dp 可分为 计数 dp 和数位统计 dp.大多是用来统计方案数什么的,特别强调 不重不漏,在此还是根据各个题的特点将计数 dp 和数位 dp 分开整理.其实数位 dp 的题目会相对多很多- ...
- idea导入maven项目结构不全
本文为博主原创,转载请注明出处 将本地的项目导入idea中,其操作第一步为: File->open->选中导入maven项目的pom文件,正常情况通过该步骤项目就会导入到idea中. 通过 ...
- bootstrap : 解决使图片全屏显示有空白边距的问题
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8 ...
- 如何部署两个JMS网关,形成双机热备
大家使用JMS的过程中,可能会留意到,不管是微服务在注册时,还是RemoteClient构造时,所指向的网关都是一个NetAddress数组,之所以网关地址是多个,而不是一个,那是因为网关是一个双击热 ...
- 理解Asp.net MVC模式
MVC是模型(Model).视图(View).控制器(Controller)的缩写,它是Web应用程序中一种常用的架构模式.这种模式将应用程序大体上分为3层,即Model层.View层和Control ...
- [转帖]ntp导致的时钟回拨
https://zhuanlan.zhihu.com/p/587313130 我们的服务器时间校准一般是通过ntp进程去校准的.但由于校准这个动作,会导致时钟跳跃变化的现象.而这种情况里面,往往回拨最 ...
- [转帖]linux将大目录等分切割成多个小目录
https://www.jianshu.com/p/6f9e6743a1dc 需求:有一个目录存放了数十万个文件,现在需要将这个目录上传,如果整个目录上传,中间因为某些故障断开连接了,可能又要从头开始 ...
- [转帖]Kafka-LEO和HW概念及更新流程
https://www.cnblogs.com/youngchaolin/p/12641463.html 目录 LEO&HW基本概念 LEO&HW更新流程 LEO HW 更新流程示例分 ...