流程控制语句是C语言中最基本的判断语句,通常我们可以使用IF来构建多分支结构,但同样可以使用Switch语句构建,Switch语句针对多分支的优化措施有4种形式,分别是,IF-ELSE优化,有序线性优化,非线性索引优化,平衡判定树优化。

与IF语句结构不同,IF语句会在条件跳转后紧跟语句块,而SWITCH结构则将所有条件跳转都放置在一起,判断时需要重点观察每个条件跳转指令后面是否跟有语句块,以辨别SWITCH分支结构。

在switch分支数小于4的情况下,编译器将采用模拟IF-ELSE分支的方式构建SWITCH结构,这样则无法发挥出SWITCH语句的优势,当分支数大于3并且case的判断值存在明显线性关系时,Switch语句的优化特性才可以凸显出来。

有序线性优化: 该优化方式将每个case语句块的地址预先保存在数组中,并依据此数组查询case语句块对应的首地址。

首先代码生成时会为case语句制作一个case地址表数组,数组中保存每个ease语句块的首地址,并且下标以0开头,在进入switch后先进行一次比较,检查输入的数值是否大于case值的最大值,

为了达到线性有序,对于没有case对应的数值,编译器以switch的结束地址或者default语句块的首地址填充对应的表格项。

case线性地址表是一个有序表。

当switch为一个有序线性组合时,会对其case语句块制作地址表,以减少比较跳转次数。

在编写代码时,我们无需自己排列case序列,编译器编译时会自动进行排序优化,先来编写一个简单的代码:

int main(int argc, char *argv[])
{
int index = 0; scanf("%d", &index); 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;
}

代码经过反汇编后,我们可以注意到,首先用户通过scanf输入所需要执行的分支,因为分支语句下标从0开始,所以需要dec eax减去1,在进入switch语句之前,判断输入的下标是否高于6,如果高于则直接跳出switch,否则执行ds:[eax*4+0x401348]寻址。

004012B4 | FF15 B8304000            | call dword ptr ds:[<&scanf>]              |
004012BA | 8B45 FC | mov eax,dword ptr ss:[ebp-4] |
004012BD | 83C4 08 | add esp,8 |
004012C0 | 48 | dec eax | Switch语句获取比例因子,需要减1
004012C1 | 83F8 06 | cmp eax,6 | 首先对比输入数据是否大于6
004012C4 | 77 6B | ja consoleapplication.401331 | 大于则说明Switch分支无对应结构 (则直接跳转到结束)
004012C6 | FF2485 48134000 | jmp dword ptr ds:[eax*4+0x401348] | 跳转到指定代码段

地址0x401348对应的就是每一个分支的首地址,跳转后即可看到分支。

非线性索引优化: 如果两个case值间隔较大,仍然使用switch的结尾地址或default地址代替地址表中缺少的case地址,这样则会造成极大的空间浪费。

非线性的switch结构,可采用制作索引表的方式进行优化,索引表有两张,1.case语句块地址表,2.case语句块索引表。

地址表中每一项保存一个case语句块首地址,有几个case语句块或default语句块,就保存几项,结束地址在地址表中指挥保存一份。

索引表中保存了地址表中的下标值,索引表最多可容纳256项,每项1字节,所以case值不可超过1字节,索引表也只能存储256项索引编号。

在执行时需要通过索引表来查询地址表,会多出一次查表的过程,因此效率上会有所下降。

004012B4 | FF15 B8304000            | call dword ptr ds:[<&scanf>]              |
004012BA | 8B45 FC | mov eax,dword ptr ss:[ebp-4] |
004012BD | 83C4 08 | add esp,8 |
004012C0 | 48 | dec eax | Switch语句获取比例因子,需要减1
004012C1 | 3D FE000000 | cmp eax,FE | 首先对比输入数据是否大于254
004012C6 | 0F87 80000000 | ja consoleapplication.40134C | 跳转到指定代码段
004012CC | 0FB680 70134000 | movzx eax,byte ptr ds:[eax+0x401370] | 从索引表找地址表下标
004012D3 | FF2485 54134000 | jmp dword ptr ds:[eax*4+0x401354] | 比例因子寻找函数地址

首先movzx eax, byte ptr ds:[eax+0x401370]从索引表中找到地址表下标。

然后通过索引表找到索引值,并带入jmp dword ptr ds:[eax*4+0x401354]找到地址表中的指定地址,地址表中每一个地址就代表一个分支结构里的函数。

这样的优势就是可以节约空间,每一个所以表字段只占1字节,如果两个case差距较大同样会指向同一个地址表中的地址,地址表相对来说会变得简单,但这种查询方式会产生两次间接内存访问,在效率上远远低于线性表方式。

平衡判定树优化: 当最大case值与最小case值之差大于255时,则会采用判定树优化,将每个case值作为一个节点,从节点中找出中间值作为根节点,以此形成一颗平衡二叉树,以每个节点为判定值,大于和小于关系分别对应左子树和右子树,从而提高查询效率。

如果打开编译器体积优先,编译器尽量会以二叉判定树的方式来降低程序占用体积,如果无法使用以上优化方式,则需要将switch做成树。

int main(int argc, char *argv[])
{
int index = 0; scanf("%d", &index); 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;
}

判定树反汇编形式。

004012C0 | 83F8 0A                  | cmp eax,A                                 | A:'\n'
004012C3 | 7F 63 | jg consoleapplication.401328 |
004012C5 | 74 4D | je consoleapplication.401314 |
004012C7 | 83E8 02 | sub eax,2 |
004012CA | 74 34 | je consoleapplication.401300 |
004012CC | 48 | dec eax |
004012CD | 74 1D | je consoleapplication.4012EC |
004012CF | 83E8 05 | sub eax,5 |
004012D2 | 0F85 97000000 | jne consoleapplication.40136F |
004012D8 | 68 A0314000 | push consoleapplication.4031A0 | main.cpp:16, 4031A0:"index 8"
004012DD | FF15 B4304000 | call dword ptr ds:[<&printf>] | main.cpp:20
004012E3 | 83C4 04 | add esp,4 |

判定树,通过增加多条分支结构,从中位数开始判断,大于或小于分别走不同的分支,直到遇到函数地址位置。

为了降低数的高度,在优化过程中,会检查代码是否满足if-else优化,有序线性优化,非线性索引优化,利用三种优化来降低树高度,谁的效率高就优先使用谁,三种优化都无法匹配才会使用判定树。

C++ 反汇编:关于Switch语句的优化措施的更多相关文章

  1. 通过goto语句学习if...else、switch语句并简单优化

    goto语句在C语言中实现的就是无条件跳转,第二章一上来就介绍goto语句就是要通过goto语句来更加清楚直观的了解控制结构. 我理解的goto语句其实跟switch语句有相似之处,都是进行跳转.不同 ...

  2. switch 语句的反汇编浅析

    switch 的简单情景(case 不超过 3 项) 首先,我们分析一下 switch 语句的一种简单情景,我们可以用 C 写出如下如下代码. 编译后用 OllyDBG 载入,它将显示出如下的反汇编代 ...

  3. Linux 程序设计的一些优化措施

    Linux 程序设计的一些优化措施 这些知识是在平常的阅读中,零散的获得的,自己总结了一下,分享在这里 全局变量VS函数参数 全局变量在Linux下的驱动编程里边,用的是非常多,例如中断服务函数ISR ...

  4. 逆向随笔 - switch 语句深入分析

    switch case 语句在c语言里还是比較简单的.可是被编译出来之后,优化结果往往让人非常疑惑.全然看不懂,以下我们一次次的尝试,看看编译器究竟把switch语句变成什么样了.   ① 先上个最简 ...

  5. mysql的优化措施,从sql优化做起

    http://geeksblog.cc/2016/06/11/mysql-optimize/ 优化sql的一般步骤 通过show status了解各种sql的执行频率 定位执行效率低的sql语句 通过 ...

  6. switch语句分析

    1.关于switch语句 如果if语句中表达式是判断是否等于一个常量时,可以用switch语句来代替 if(表达式 == 常量1)                        {          ...

  7. Python 为什么不支持 switch 语句?

    本文出自"Python为什么"系列,请查看全部文章 在这篇文章里,我们会聊一聊为什么 Python 决定不支持 switch 语句. 为什么想要聊这个话题呢? 主要是因为 swit ...

  8. switch语句的妙用

    switch语句的普通用法很简单,如下: var a = 3; switch (a) { case 1: console.log(a); break; case 2: case 3: console. ...

  9. 106运用SWITCH语句打印星期几的单词

    package com.chongrui.test;/*运用SWITCH语句打印星期几的单词 * */ public class TypeConvertion { public static void ...

随机推荐

  1. 【剑指Offer】数字在排序数组中出现的次数 解题报告(Python)

    [剑指Offer]数字在排序数组中出现的次数 解题报告(Python) 标签(空格分隔): 剑指Offer 题目地址:https://www.nowcoder.com/ta/coding-interv ...

  2. web安全之xss跨站脚本攻击

    实验(一) 一.预备知识 1.HTML基础知识 2.phpstudy运用 3.xss的分类   二.实验环境 1.火狐浏览器.Chrome浏览器 2.phpstudy   三.环境搭建 反射型xss环 ...

  3. JSON(JS 对象简谱,一种数据交换格式)

    JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式 存储和表示数据的文本格式 层次结构清晰.简洁 JSON是一个序列化的对象或数组 1.js ...

  4. MySQL 高级内容

    MyISAM 和 MEMORY 存储引擎支持表级锁定(table-level locking),InnoDB 存储引擎支持行级锁定(row-level locking),BDB 存储引擎支持页级锁定( ...

  5. Python基础入门(8)- Python模块和包

    1.包与模块的定义与导入 1.1.什么是python的包与模块 包就是文件夹,包中还可以有包,也就是子文件夹 一个个python文件模块 1.2.包的身份证 __init__.py是每一个python ...

  6. Linux进程管理之基本指令

    目录 基本介绍 显示系统执行的进程 指令 ps - aux 常用选项 每行栏目的含义 查看父进程 终止进程 相关指令 实用案例 踢掉某个非法登录用户 终止远程登录服务sshd,在适当的时候再次重启ss ...

  7. 编写Java程序,使用Set实现不重复添加用户

    返回本章节 返回作业目录 需求说明: 在控制台输入用户信息,用户信息包括姓名.性别和年龄,将用户信息保存至User对象中. 将User对象保存至HashSet集合中. 规定如果两个User对象的姓名. ...

  8. ProtoBuf3语法指南(Protocol Buffers)_下

    0.说明 ProtoBuf3语法指南, 又称为proto3, 是谷歌的Protocol Buffers第3个版本. 本文基于官方英文版本翻译, 加上了自己的理解少量修改, 一共分为上下两部分. 1.A ...

  9. SpringCloud创建Eureka模块

    1.说明 本文详细介绍Spring Cloud创建Eureka模块的方法, 基于已经创建好的Spring Cloud父工程, 请参考SpringCloud创建项目父工程, 在里面创建Eureka模块, ...

  10. Linux 安装并启用 PHP-FPM

    首先,在编译时带上 --enable-fpm 参数: [root@localhost local]# yum -y install libxml2 libxml2-devel gd gd-devel ...