switch语句详解及底层实现原理

news2024/11/19 3:19:51

目录

switch 与 if else

switch语句用法

switch底层汇编实现分析

switch原理总结


switch 与 if else

if else是人工优化的,而switch则是编译器进行优化的

使用场合:命中样本一致,每个case命中概率一样,case的数据必须是线性的,可以缺,但不能缺太多  而 if else 的某个分支结构的命中则是必须要经过前面的。

swich优化有4中策略,case<1,走方案一,大于3才走别的。这里先学习第二种优化

switch语句用法

基本格式

switch(表达式)
{
case 常量1:语句1
case 常量2:语句2
default:语句n   
    break;
}

样例

// swith语句底层

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int n = 3;
	switch (n)
	{
	case 0:
		printf("n == 0\r\n");
		break;
	case 1:
		printf("n == 1\r\n");
		break;
	case 2:
		printf("n == 2\r\n");
		break;
	case 3:
		printf("n == 3\r\n");
		break;
	case 4:
		printf("n == 4\r\n");
		break;
	case 5:
		printf("n == 5\r\n");
		break;
	default:
		printf("default\r\n");
	}

	system("pause");
	return 0;
}

switch底层汇编实现分析

C代码如下:

#include <stdio.h>
#include <stdlib.h>

int main()
{
	int n = 3;
	switch (n)
	{
	case 0:
		printf("n == 0\r\n");
		break;
	case 1:
		printf("n == 1\r\n");
		break;
	case 2:
		printf("n == 2\r\n");
		break;
	case 3:
		printf("n == 3\r\n");
		break;
	case 4:
		printf("n == 4\r\n");
		break;
	case 5:
		printf("n == 5\r\n");
		break;
	default:
		printf("default\r\n");
	}

	system("pause");
	return 0;
}

switch结构汇编代码

	switch (n)
009B187C 8B 45 F8             mov         eax,dword ptr [n]  
009B187F 89 85 30 FF FF FF    mov         dword ptr [ebp-0D0h],eax  
009B1885 83 BD 30 FF FF FF 05 cmp         dword ptr [ebp-0D0h],5  
009B188C 77 67                ja          $LN9+0Fh (09B18F5h)  
009B188E 8B 8D 30 FF FF FF    mov         ecx,dword ptr [ebp-0D0h]  
009B1894 FF 24 8D 30 19 9B 00 jmp         dword ptr [ecx*4+9B1930h]  
	{
	case 0:
		printf("n == 0\r\n");
009B189B 68 E0 7B 9B 00       push        offset string "n == 0\r\n" (09B7BE0h)  
009B18A0 E8 28 F8 FF FF       call        _printf (09B10CDh)  
009B18A5 83 C4 04             add         esp,4  
		break;
009B18A8 EB 58                jmp         $LN9+1Ch (09B1902h)  
	case 1:
		printf("n == 1\r\n");
009B18AA 68 EC 7B 9B 00       push        offset string "n == 1\r\n" (09B7BECh)  
009B18AF E8 19 F8 FF FF       call        _printf (09B10CDh)  
009B18B4 83 C4 04             add         esp,4  
		break;
009B18B7 EB 49                jmp         $LN9+1Ch (09B1902h)  
	case 2:
		printf("n == 2\r\n");
009B18B9 68 30 7B 9B 00       push        offset string "* " (09B7B30h)  
009B18BE E8 0A F8 FF FF       call        _printf (09B10CDh)  
009B18C3 83 C4 04             add         esp,4  
		break;
009B18C6 EB 3A                jmp         $LN9+1Ch (09B1902h)  
	case 3:
		printf("n == 3\r\n");
009B18C8 68 E0 7C 9B 00       push        offset string "n == 3\r\n" (09B7CE0h)  
009B18CD E8 FB F7 FF FF       call        _printf (09B10CDh)  
009B18D2 83 C4 04             add         esp,4  
		break;
009B18D5 EB 2B                jmp         $LN9+1Ch (09B1902h)  
	case 4:
		printf("n == 4\r\n");
009B18D7 68 EC 7C 9B 00       push        offset string "n == 4\r\n" (09B7CECh)  
009B18DC E8 EC F7 FF FF       call        _printf (09B10CDh)  
009B18E1 83 C4 04             add         esp,4  
		break;
009B18E4 EB 1C                jmp         $LN9+1Ch (09B1902h)  
	case 5:
		printf("n == 5\r\n");
009B18E6 68 F8 7C 9B 00       push        offset string "n == 5\r\n" (09B7CF8h)  
009B18EB E8 DD F7 FF FF       call        _printf (09B10CDh)  
009B18F0 83 C4 04             add         esp,4  
		break;
009B18F3 EB 0D                jmp         $LN9+1Ch (09B1902h)  
	default:
		printf("default\r\n");
009B18F5 68 04 7D 9B 00       push        offset string "default\r\n" (09B7D04h)  
009B18FA E8 CE F7 FF FF       call        _printf (09B10CDh)  
009B18FF 83 C4 04             add         esp,4  

分析switch部分汇编代码解读

mov         eax,dword ptr [n]               // 把n存储的数据交给eax
mov         dword ptr [ebp-0D0h],eax        // 把eax寄存器的数据到以ebp-0D0h为地址的内存位置中 
cmp         dword ptr [ebp-0D0h],5          // 比较两个值
ja          $LN9+0Fh (09B18F5h)             // 地址跳转
mov         ecx,dword ptr [ebp-0D0h]        
jmp         dword ptr [ecx*4+9B1930h]       // 该指令将无条件跳转到这个地址,继续执行后续的指令

更加具体的细节

  • dword ptr是一个指令修饰符,用于指示内存操作数的大小为双字(32位)。
  • eax是寄存器,用于存储数据
  • [n] 指n的地址
  • [ebp-0D0h] 表示以基址寄存器 ebp 减去偏移 0D0h 所得到的内存地址。
  • ebp 是栈基指针寄存器,-0D0h 是一个偏移量,用于定位相对于 ebp 的内存位置。
  • ecx 寄存器经常被用作循环计数器,特别是在汇编语言中经常使用 ecx 寄存器来实现循环操作。它还可以用于存储临时数据、中间结果以及其他需要在计算过程中使用的值。
  • [ecx*4+9B1930h]:将寄存器 ecx 中的值乘以 4,并加上偏移量 9B1930h,得到一个内存地址。然后,该指令将无条件跳转到这个地址,继续执行后续的指令。
  • ja $LN9+0Fh (09B18F5h) 是一个条件跳转指令,用于根据处理器标志寄存器中的条件进行无条件跳转。
  • ja 是无符号大于条件跳转指令,当处理器标志寄存器中的条件满足无符号大于(CF=0且ZF=0)时,会执行跳转操作。
  • ebp 寄存器通常用于存储当前函数的基址指针,指向当前函数的栈帧的底部。栈帧是用于存储局部变量、函数参数和返回地址等信息的一块内存区域。
  • 这里数据后面的h表示是16进制

swith汇编处理的逻辑:

  1. 根据传入的n值,mov到指定的地址
  2. 进行有条件的比较,根据条件跳转地址
  3. 再把n的值到ecx里面
  4. 经过计算得到一个偏移的地址并跳转
  5. 通过内存最后内存地址计算,并且在反汇编页面进行跳转到case 3的代码位置

因为case的汇编代码都大差不差,所以直接阅读case 3的代码

push        offset string "n == 3\r\n" (09B7CE0h)   // 把字符串的地址送到栈中
call        _printf (09B10CDh)                      // 将当前指令的下一条指令的地址(即返回地址)推送到栈中,并跳转到 _printf 函数的入口地址开始执行。
add         esp,4                                   // 将栈指针 esp 的值增加 4 字节(一个字)
		break;
jmp         $LN9+1Ch (09B1902h)                     // 无条件地跳转到地址为 09B1902h 的目标位置继续执行后续的指令。

更加具体的细节:

  • esp 是x86架构中的栈指针寄存器,也称为堆栈指针寄存器(Stack Pointer Register)。栈是一种数据结构,用于存储临时数据、函数参数、返回地址和局部变量等信息。
  • 使用 offset 运算符可以获取一个标号或字符串的内存地址,并将该地址推送(存储)到栈中。这在处理字符串和传递参数时非常常见。
  • call 是一个用于调用函数或子程序的指令。当执行 call 指令时,它会将当前指令的下一条指令的地址(即返回地址)推送到栈中,并跳转到指定的函数入口地址开始执行。也就说会把下一条add指令移动栈中作为参数传递

case部分处理逻辑:

  1. 把字符串的地址送到栈中、
  2. call指令调用输出的函数,并把下一条的add指令放到栈中
  3. 执行函数后,无条件跳转到别的地址

switch原理总结

通过阅读switch汇编实现源码,可以总结出它的实现原理

首先,它实现了一个表结构,用来记录每个case汇编代码的首地址

 这个表结构如何生成?

是根据case的值确定下标,所以就解释了case的值为什么必须要求是整形或者类整形的原因了。

case的值确定下标,存储case代码的地址相对应

下图是在内存中表的存储

 之后switch会把case的值*4(因为4个字节存储地址)作为0x9b1930的偏移量到对应的case地址。之后就是case内部具体实现逻辑了。

这个图的意思是要说:
表建立的下标必然是根据case从小到大的线性排布建立的,但是代码的位置并不一样,因为代码的书写上并不一定是按照的case从小到大线性排布的。

这是第一次阅读底层汇编实现代码,感觉还有很多疏漏之处,比如:函数调用时的栈处理流程和内存变化,printf函数实现原理,包括汇编指令的了解都存在细节上的问题。

正所谓万事开头难,相信随着后续的学习,认识不断增加,会再次更深入了解switch实现原理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1417522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内网安全:RDP WinRS WinRM SPN Kerberos 横向移动

目录 WinRM协议 RDP协议 域横向移动&#xff1a;RDP协议 RDP协议利用 一. 探针服务 二. 获取NTML Hash 明文密码 三. 连接执行 域横向移动&#xff1a;WinRM WinRS WinRM协议、WinRS命令利用 一. cs 内置端口扫描5985 二. 连接执行 三. 上线CS 四. CS插件横向移动…

日常学习之:vue + django + docker + heroku 对后端项目 / 前后端整体项目进行部署

文章目录 使用 docker 在 heroku 上单独部署 vue 前端使用 docker 在 heroku 上单独部署 django 后端创建 heroku 项目构建 Dockerfile设置 settings.pydatabase静态文件管理安全设置applicaiton & 中间件配置 设置 requirements.txtheroku container 部署应用 前后端分别部…

解读BEVFormer,新一代自动驾驶视觉工作的基石

文章出处 BEVFormer这篇文章很有划时代的意义&#xff0c;改变了许多视觉领域工作的pipeline[2203.17270] BEVFormer: Learning Birds-Eye-View Representation from Multi-Camera Images via Spatiotemporal Transformers (arxiv.org)https://arxiv.org/abs/2203.17270 BEV …

深入理解C语言(3):自定义类型详解

文章主题&#xff1a;结构体类型详解&#x1f30f;所属专栏&#xff1a;深入理解C语言&#x1f4d4;作者简介&#xff1a;更新有关深入理解C语言知识的博主一枚&#xff0c;记录分享自己对C语言的深入解读。&#x1f606;个人主页&#xff1a;[₽]的个人主页&#x1f3c4;&…

使用毫米波雷达传感器的功能安全兼容系统设计指南1(TI文档)

摘要 功能安全标准规定了在系统中实施安全的要求&#xff0c;并有助于概括该系统要达到的安全目标。包括功能安全的系统设计不仅要降低操作不当的风险&#xff0c;还要检测故障并将其影响降到最低。随着汽车和工业系统的自主性越来越强&#xff0c;严格的功能安全要求被强制执行…

docker中安装seata,以nacos为配置中心

docker中安装seata&#xff0c;以nacos为配置中心 一、环境二、拉取seata镜像1、查看seata有哪些镜像2、查看原来有没有seata镜像3、拉取最新版本4、拉取指定版本 三、配置seata1、创建seata相关的数据库2、创建seata配置文件目录3、启动seata容器4、复制seata容器下的配置文件…

leetcode刷题(剑指offer) 509.斐波那契数

509.斐波那契数 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0&#xff0c;F(1) 1 F(n) F(n - 1) F(n - 2)&#xff0c;其中 n…

读书笔记:九句耐人寻味的话

“情商一定是让别人和自己都舒服。如果让别人舒服&#xff0c;自己却很痛苦&#xff0c;那不叫情商&#xff0c;叫智障。” Emotional intelligence must be about making both others and oneself comfortable. If it makes others comfortable but oneself miserable, thats …

盛最多水的容器[中等]

一、题目 给定一个长度为n的整数数组height。有n条垂线&#xff0c;第i条线的两个端点是(i, 0)和(i, height[i])。找出其中的两条线&#xff0c;使得它们与x轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。也就是求x轴与y轴的面积。 说明&#xff1a;你不能倾…

Threejs 展示——obj 格式模型导入

文章目录 需求分析1. HTML版本2. Vue 版本 需求 导入obj 格式的模型数据 分析 .obj&#xff1a;Wavefront OBJ 格式&#xff0c;是一种广泛使用的三维模型文件格式。预览 .obj格式文件的软件可点此下载需要准备两种格式的数据&#xff0c;如下所示 1. HTML版本 html <!…

电脑和手机连接酒店的wifi,网络不通导致charles无法抓手机的包

查看苹果手机&#xff0c;连wifi后的ip地址 电脑去ping 手机的ip地址&#xff0c;发现ping不通 解决方案&#xff1a; 应该是酒店wifi的问题&#xff0c;让朋友开个手机热点&#xff0c;电脑和我的手机都连这个热点&#xff0c;就可以抓包了

13.Golang中面向对象的多态及基本要素

目录 概述实践多态实现代码结果 基本要素 结束 概述 Golang中类的表示与封装继承 用这种方式并不能实现多态 需要结合 interface 来实现。 实践 多态实现 代码 package mainimport "fmt"type AnimalIF interface {// 这两个方法&#xff0c;实现类&#xff0c;必…

python笔记10

1、继承 继承是面向对象编程中的一个重要概念&#xff0c;它允许一个类&#xff08;子类&#xff09;继承另一个类&#xff08;父类&#xff09;的属性和方法。通过继承&#xff0c;子类可以重用父类的代码&#xff0c;并且有机会添加新的属性和方法&#xff0c;或者重写父类的…

【C++】一题掌握空指针

今天看见一道面试题&#xff0c;比较有意思&#xff0c;这一分享出来&#xff1a; 1.下面程序能编译通过吗&#xff1f; 2.下面程序会崩溃吗&#xff1f;在哪里崩溃 class A {public:void PrintA(){cout<<_a<<endl;}void Show(){cout<<"Show()"&…

.ui文件相关

目录 ui类生成过程&#xff1a; 提问&#xff1a; 等以后自己熟练了用代码写这些样式内容&#xff0c;尽量用代码写&#xff0c;原因很简单&#xff1a; 用代码写的可以直接修改代码&#xff0c;但是在设计界面修改的东西&#xff0c;电脑没有QC这玩意&#xff0c;还真不好改…

ChatGPT4 比 ChatGPT3.5 强在了那里?

刚开始的时候我还在纠结&#xff0c;一个月20 刀的ChatGPT4 &#xff0c;到底值不值这个价钱&#xff1f;使用过后发现&#xff0c;诶嘛真香。因为 GPT4 比 GPT3.5 多了太多功能&#xff0c;特别是识图能力&#xff0c;用好的话效率翻倍。 1. 看图写代码 ChatGPT4 相比 ChatG…

【极数系列】Flink集成DataSource读取集合数据(07)

文章目录 01 引言02 简介概述03 基于集合读取数据3.1 集合创建数据流3.2 迭代器创建数据流3.3 给定对象创建数据流3.4 迭代并行器创建数据流3.5 基于时间间隔创建数据流3.6 自定义数据流 04 源码实战demo4.1 pom.xml依赖4.2 创建集合数据流作业4.3 运行结果日志 01 引言 源码地…

用ASM HEMT模型提取GaN器件的参数

标题&#xff1a;Physics-Based Multi-Bias RF Large-Signal GaNHEMT Modeling and Parameter Extraction Flow (JEDS 17年) 模型描述 该模型的核心是对表面势&#xff08;ψ&#xff09;及其随施加的栅极电压&#xff08;Vg&#xff09;和漏极电压&#xff08;Vd&#xff09…

【数据结构1-3】集合

有时候&#xff0c;我们并不关心数据之间的前后关系&#xff0c;也不关心数据的层次关系。一些确定元素只是单纯的聚集在一起&#xff0c;这样的元素聚集体被称为集合。 当希望知道某个数据是否存在一个集合中&#xff0c;或者两个元素是否在同一个集合中时&#xff0c;就需要使…

JVM系列——对象管理

JVM对象分布 对象头 第一类是用于存储对象自身的运行时数据&#xff0c;如哈希码&#xff08;HashCode&#xff09;、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等 另外一部分是类型指针&#xff0c;即对象指向它的类型元数据的指针&#xff0c;Java 虚…