程序员需要了解的硬核知识之汇编语言(全)(四)

简介: 之前的系列文章从 CPU 和内存方面简单介绍了一下汇编语言,但是还没有系统的了解一下汇编语言,汇编语言作为第二代计算机语言,会用一些容易理解和记忆的字母,单词来代替一个特定的指令,作为高级编程语言的基础,有必要系统的了解一下汇编语言,那么本篇文章希望大家跟我一起来了解一下汇编语言。

循环控制语句的处理

上面说的都是顺序流程,那么现在就让我们分析一下循环流程的处理,看一下 for 循环以及 if 条件分支等 c 语言程序的 流程控制是如何实现的,我们还是以代码以及编译后的结果为例,看一下程序控制流程的处理过程。

// 定义MySub 函数
void MySub(){
  // 不做任何处理
  
}
// 定义MyFunc 函数
void Myfunc(){
  int i;
  for(int i = 0;i < 10;i++){
    // 重复调用MySub十次
    MySub();
  }
}

上述代码将局部变量 i 作为循环条件,循环调用十次MySub 函数,下面是它主要的汇编代码

xor         ebx, ebx    ; 将寄存器清0

@4 call _MySub ; 调用MySub函数
inc ebx ; ebx寄存器的值 + 1
cmp ebx,10 ; 将ebx寄存器的值和10进行比较
jl short @4 ; 如果小于10就跳转到 @4

C 语言中的 for 语句是通过在括号中指定循环计数器的初始值(i = 0)、循环的继续条件(i < 10)、循环计数器的更新(i++) 这三种形式来进行循环处理的。与此相对的汇编代码就是通过比较指令(cmp)跳转指令(jl)来实现的。

下面我们来对上述代码进行说明

MyFunc 函数中用到的局部变量只有 i ,变量 i 申请分配了 ebx 寄存器的内存空间。for 语句括号中的 i = 0 被转换为 xor ebx,ebx 这一处理,xor 指令会对左起第一个操作数和右起第二个操作数进行 XOR 运算,然后把结果存储在第一个操作数中。由于这里把第一个操作数和第二个操作数都指定为了 ebx,因此就变成了对相同数值的 XOR 运算。也就是说不管当前寄存器的值是什么,最终的结果都是0。类似的,我们使用 mov ebx,0 也能得到相同的结果,但是 xor 指令的处理速度更快,而且编译器也会启动最优化功能。

XOR 指的就是异或操作,它的运算规则是 如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0

相同数值进行 XOR 运算,运算结果为0。XOR 的运算规则是,值不同时结果为1,值相同时结果为0。例如 01010101 和 01010101 进行运算,就会分别对各个数字位进行 XOR 运算。因为每个数字位都相同,所以运算结果为0。

ebx 寄存器的值初始化后,会通过 call 指定调用 _MySub 函数,从 _MySub 函数返回后,会执行inc ebx 指令,对 ebx 的值进行 + 1 操作,这个操作就相当于 i++ 的意思,++ 表示的就是当前数值 + 1。

这里需要知道 i++ 和 ++i 的区别

i++ 是先赋值,复制完成后再对 i执行 + 1 操作

++i 是先进行 +1 操作,完成后再进行赋值

inc 下一行的 cmp 是用来对第一个操作数和第二个操作数的数值进行比较的指令。cmp ebx,10 就相当于 C 语言中的 i < 10 这一处理,意思是把 ebx 寄存器的值与10进行比较。汇编语言中比较指令的结果,会存储在 CPU 的标志寄存器中。不过,标志寄存器的值,程序是无法直接参考的。那如何判断比较结果呢?

汇编语言中有多个跳转指令,这些跳转指令会根据标志寄存器的值来判断是否进行跳转操作,例如最后一行的 jl,它会根据 cmp ebx,10 指令所存储在标志寄存器中的值来判断是否跳转,jl 这条指令表示的就是 jump on less than(小于的话就跳转)。发现如果 i 比 10 小,就会跳转到 @4 所在的指令处继续执行。

那么汇编代码的意思也可以用 C 语言来改写一下,加深理解

i ^= i;
L4: MySub();
i++;
if(i < 10) goto L4;

代码第一行 i ^= i 指的就是 i 和 i 进行异或运算,也就是 XOR 运算,MySub() 函数用 L4 标签来替代,然后进行 i 自增操作,如果i 的值小于 10 的话,就会一直循环 MySub() 函数。

条件分支的处理方法

条件分支的处理方式和循环的处理方式很相似,使用的也是 cmp 指令和跳转指令。下面是用 C 语言编写的条件分支的代码

// 定义MySub1 函数
void MySub1(){
// 不做任何处理
}
// 定义MySub2 函数
void MySub2(){

// 不做任何处理
}
// 定义MySub3 函数
void MySub3(){
// 不做任何处理
}
// 定义MyFunc 函数
void MyFunc(){
int a = 123;
// 根据条件调用不同的函数
if(a > 100){
MySub1();
}
else if(a < 50){
MySub2();
}
else
{
MySub3();
}
}

很简单的一个实现了条件判断的 C 语言代码,那么我们把它用 Borland C++ 编译之后的结果如下

_MyFunc proc near
push ebp
mov ebp,esp
mov eax,123 ; 把123存入 eax 寄存器中
cmp eax,100 ; 把 eax 寄存器的值同100进行比较
jle short @8 ; 比100小时,跳转到@8标签
call _MySub1 ; 调用MySub1函数
jmp short @11 ; 跳转到@11标签
@8:
cmp eax,50 ; 把 eax 寄存器的值同50进行比较
jge short @10 ; 比50大时,跳转到@10标签
call _MySub2 ; 调用MySub2函数
jmp short @11 ; 跳转到@11标签
@10:
call _MySub3 ; 调用MySub3函数
@11:
pop ebp
ret
_MyFunc endp

上面代码用到了三种跳转指令,分别是jle(jump on less or equal) 比较结果小时跳转,jge(jump on greater or equal) 比较结果大时跳转,还有不管结果怎样都会进行跳转的jmp,在这些跳转指令之前还有用来比较的指令 cmp,构成了上述汇编代码的主要逻辑形式。

了解程序运行逻辑的必要性

通过对上述汇编代码和 C 语言源代码进行比较,想必大家对程序的运行方式有了新的理解,而且,从汇编源代码中获取的知识,也有助于了解 Java 等高级语言的特性,比如 Java 中就有 native 关键字修饰的变量,那么这个变量的底层就是使用 C 语言编写的,还有一些 Java 中的语法糖只有通过汇编代码才能知道其运行逻辑。在某些情况下,对于查找 bug 的原因也是有帮助的。

上面我们了解到的编程方式都是串行处理的,那么串行处理有什么特点呢?


12.png


串行处理最大的一个特点就是专心只做一件事情,一件事情做完之后才会去做另外一件事情。

计算机是支持多线程的,多线程的核心就是 CPU切换,如下图所示

13.jpg


我们还是举个实际的例子,让我们来看一段代码

// 定义全局变量
int counter = 100;
// 定义MyFunc1()
void MyFunc(){
counter *= 2;
}
// 定义MyFunc2()
void MyFunc2(){
counter *= 2;
}

上述代码是更新 counter 的值的 C 语言程序,MyFunc1() 和 MyFunc2() 的处理内容都是把 counter 的值扩大至原来的二倍,然后再把 counter 的值赋值给 counter 。这里,我们假设使用多线程处理,同时调用了一次MyFunc1 和 MyFunc2 函数,这时,全局变量 counter 的值,理应变成 100 2 2 = 400。如果你开启了多个线程的话,你会发现 counter 的数值有时也是 200,对于为什么出现这种情况,如果你不了解程序的运行方式,是很难找到原因的。

我们将上面的代码转换成汇编语言的代码如下

mov eax,dword ptr[_counter]     ; 将 counter 的值读入 eax 寄存器
add eax,eax ; 将 eax 寄存器的值扩大2倍。
mov dword ptr[_counter],eax ; 将 eax 寄存器的值存入 counter 中。

在多线程程序中,用汇编语言表示的代码每运行一行,处理都有可能切换到其他线程中。因而,假设 MyFun1 函数在读出 counter 数值100后,还未来得及将它的二倍值200写入 counter 时,正巧 MyFun2 函数读出了 counter 的值100,那么结果就将变为 200 。

14.jpg

为了避免该bug,我们可以采用以函数或 C 语言代码的行为单位来禁止线程切换的锁定方法,或者使用某种线程安全的方式来避免该问题的出现。

现在基本上没有人用汇编语言来编写程序了,因为 C、Java等高级语言的效率要比汇编语言快很多。不过,汇编语言的经验还是很重要的,通过借助汇编语言,我们可以更好的了解计算机运行机制。


            </div>
目录
相关文章
|
存储 算法 NoSQL
天呐!汇编语言竟如此神奇,从零到精通的学习指南带你开启计算机世界神秘大门!
【8月更文挑战第31天】汇编语言是一种底层编程语言,直接与硬件交互,对于理解计算机体系结构和底层原理至关重要。尽管现代软件开发中较少使用,但学习汇编语言有助于深入了解计算机如何执行指令、管理内存和处理数据,从而优化程序性能,进行底层系统开发和调试。不同处理器有不同指令集,如 x86 和 ARM,掌握这些指令集及寄存器、内存地址等基本概念是学习汇编语言的基础。通过简单示例开始,逐步掌握复杂指令和调试工具,可以大大提高编程技能和解决问题的能力。
352 1
|
9天前
|
数据采集 人工智能 安全
|
4天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化
随机森林是一种基于决策树的集成学习算法,通过构建多棵决策树并结合它们的预测结果来提高准确性和稳定性。其核心思想包括两个随机性:Bootstrap采样(每棵树使用不同的训练子集)和特征随机选择(每棵树分裂时只考虑部分特征)。这种方法能有效处理大规模高维数据,避免过拟合,并评估特征重要性。随机森林的超参数如树的数量、最大深度等可通过网格搜索优化。该算法兼具强大预测能力和工程化优势,是机器学习中的常用基础模型。
299 164
|
3天前
|
机器学习/深度学习 自然语言处理 机器人
阿里云百炼大模型赋能|打造企业级电话智能体与智能呼叫中心完整方案
畅信达基于阿里云百炼大模型推出MVB2000V5智能呼叫中心方案,融合LLM与MRCP+WebSocket技术,实现语音识别率超95%、低延迟交互。通过电话智能体与座席助手协同,自动化处理80%咨询,降本增效显著,适配金融、电商、医疗等多行业场景。
313 155
|
12天前
|
SQL 自然语言处理 调度
Agent Skills 的一次工程实践
**本文采用 Agent Skills 实现整体智能体**,开发框架采用 AgentScope,模型使用 **qwen3-max**。Agent Skills 是 Anthropic 新推出的一种有别于mcp server的一种开发方式,用于为 AI **引入可共享的专业技能**。经验封装到**可发现、可复用的能力单元**中,每个技能以文件夹形式存在,包含特定任务的指导性说明(SKILL.md 文件)、脚本代码和资源等 。大模型可以根据需要动态加载这些技能,从而扩展自身的功能。目前不少国内外的一些框架也开始支持此种的开发方式,详细介绍如下。
868 6