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

简介: 之前的系列文章从 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等高级语言的效率要比汇编语言快很多。不过,汇编语言的经验还是很重要的,通过借助汇编语言,我们可以更好的了解计算机运行机制。


相关文章
|
3月前
|
开发者 持续交付 Android开发
Xamarin开发者的秘密武器:如何通过持续集成与持续部署(CI/CD)实现高效、高质量的软件交付
【8月更文挑战第31天】在当今追求高效、高质量软件交付的时代,Xamarin开发者需像大厨般迅速烹制数字化佳肴,而持续集成(CI)与持续部署(CD)则是关键工具。CI要求开发者频繁将代码集成到共享仓库,利用自动化工具如Azure Pipelines或Jenkins自动编译、测试代码,确保质量。CD在此基础上进一步实现自动化部署,简化从开发到生产的全过程。借助如Visual Studio App Center这样的工具,Xamarin项目得以快速构建、测试并部署至Android和iOS平台,显著提升开发效率和代码质量,助力团队乘风破浪,驶向成功的彼岸。
31 0
|
存储 安全 Java
程序员需要了解的硬核知识之汇编语言(全)(四)
之前的系列文章从 CPU 和内存方面简单介绍了一下汇编语言,但是还没有系统的了解一下汇编语言,汇编语言作为第二代计算机语言,会用一些容易理解和记忆的字母,单词来代替一个特定的指令,作为高级编程语言的基础,有必要系统的了解一下汇编语言,那么本篇文章希望大家跟我一起来了解一下汇编语言。
214 0
程序员需要了解的硬核知识之汇编语言(全)(四)
|
存储 编译器 程序员
程序员需要了解的硬核知识之汇编语言(全)(三)
之前的系列文章从 CPU 和内存方面简单介绍了一下汇编语言,但是还没有系统的了解一下汇编语言,汇编语言作为第二代计算机语言,会用一些容易理解和记忆的字母,单词来代替一个特定的指令,作为高级编程语言的基础,有必要系统的了解一下汇编语言,那么本篇文章希望大家跟我一起来了解一下汇编语言。
142 0
程序员需要了解的硬核知识之汇编语言(全)(三)
|
存储 程序员 编译器
程序员需要了解的硬核知识之汇编语言(全)(二)
之前的系列文章从 CPU 和内存方面简单介绍了一下汇编语言,但是还没有系统的了解一下汇编语言,汇编语言作为第二代计算机语言,会用一些容易理解和记忆的字母,单词来代替一个特定的指令,作为高级编程语言的基础,有必要系统的了解一下汇编语言,那么本篇文章希望大家跟我一起来了解一下汇编语言。
83 0
程序员需要了解的硬核知识之汇编语言(全)(二)
|
编译器 程序员 C语言
程序员需要了解的硬核知识之汇编语言(全)(一)
之前的系列文章从 CPU 和内存方面简单介绍了一下汇编语言,但是还没有系统的了解一下汇编语言,汇编语言作为第二代计算机语言,会用一些容易理解和记忆的字母,单词来代替一个特定的指令,作为高级编程语言的基础,有必要系统的了解一下汇编语言,那么本篇文章希望大家跟我一起来了解一下汇编语言。
147 0
程序员需要了解的硬核知识之汇编语言(全)(一)
|
存储 程序员 编译器
程序员需要了解的硬核知识之汇编语言(一)(下)
之前的系列文章从 CPU 和内存方面简单介绍了一下汇编语言,但是还没有系统的了解一下汇编语言,汇编语言作为第二代计算机语言,会用一些容易理解和记忆的字母,单词来代替一个特定的指令,作为高级编程语言的基础,有必要系统的了解一下汇编语言,那么本篇文章希望大家跟我一起来了解一下汇编语言。
143 0
程序员需要了解的硬核知识之汇编语言(一)(下)
|
编译器 程序员 C语言
程序员需要了解的硬核知识之汇编语言(一)(上)
之前的系列文章从 CPU 和内存方面简单介绍了一下汇编语言,但是还没有系统的了解一下汇编语言,汇编语言作为第二代计算机语言,会用一些容易理解和记忆的字母,单词来代替一个特定的指令,作为高级编程语言的基础,有必要系统的了解一下汇编语言,那么本篇文章希望大家跟我一起来了解一下汇编语言。
98 0
程序员需要了解的硬核知识之汇编语言(一)(上)
|
存储 编译器 程序员
程序员需要了解的硬核知识之汇编语言(全)(三)
之前的系列文章从 CPU 和内存方面简单介绍了一下汇编语言,但是还没有系统的了解一下汇编语言,汇编语言作为第二代计算机语言,会用一些容易理解和记忆的字母,单词来代替一个特定的指令,作为高级编程语言的基础,有必要系统的了解一下汇编语言,那么本篇文章希望大家跟我一起来了解一下汇编语言。
程序员需要了解的硬核知识之汇编语言(全)(三)
|
存储 程序员 编译器
程序员需要了解的硬核知识之汇编语言(全)(二)
之前的系列文章从 CPU 和内存方面简单介绍了一下汇编语言,但是还没有系统的了解一下汇编语言,汇编语言作为第二代计算机语言,会用一些容易理解和记忆的字母,单词来代替一个特定的指令,作为高级编程语言的基础,有必要系统的了解一下汇编语言,那么本篇文章希望大家跟我一起来了解一下汇编语言。
程序员需要了解的硬核知识之汇编语言(全)(二)
|
编译器 程序员 C语言
程序员需要了解的硬核知识之汇编语言(全)(一)
之前的系列文章从 CPU 和内存方面简单介绍了一下汇编语言,但是还没有系统的了解一下汇编语言,汇编语言作为第二代计算机语言,会用一些容易理解和记忆的字母,单词来代替一个特定的指令,作为高级编程语言的基础,有必要系统的了解一下汇编语言,那么本篇文章希望大家跟我一起来了解一下汇编语言。
程序员需要了解的硬核知识之汇编语言(全)(一)