C/C++教程

《汇编语言》chapter3 note

本文主要是介绍《汇编语言》chapter3 note,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

第三章:程序设计初步

目录

第三章:程序设计初步

堆栈的作用

过程调用和返回指令

参数传递

局部变量

算术逻辑运算指令

乘除运算指令

符号扩展指令

扩展传送指令

逻辑运算指令

一般移位指令

循环移位指令

双精度移位指令

分支程序设计

无条件和条件转移指令

多路分支的实现

循环程序设计

循环指令

计数器转移指令

多重循环程序

子程序设计

传递参数方法

安排局部变量地方法

保护寄存器的约定


堆栈的作用

  • 保存函数的返回地址
  • 用于向函数传递参数
  • 安排函数的局部变量

过程调用和返回指令

CALL LABEL

  1. 不影响状态标志
  2. 段内直接调用指令:①把返回地址偏移压入堆栈;②使得EIP的内容为目标地址偏移(实现转移,和JMP指令操作是一样的)
  3. 段内间接调用指令:CALL OPRD 由OPRD给出入口地址偏移的子程序,把OPRD的内容送到EIP,操作数可以是32位的通用寄存器或者双字存储单元。
  4. 段间调用指令:相似,先把CS压入堆栈,再把EIP压入堆栈。

RET

  1. 不影响标志寄存器中的状态标志
  2. 从堆栈弹出地址偏移(调整ESP),送到指令指针寄存器EIP
  3. 段内带立即数返回指令:RET count ,其中count是一个16位的立即数,第一步不变,再把count加到ESP上
  4. 函数的返回值在寄存器EAX中

  • 调用子程序的过程本质上是控制转移
  • 在保护方式下(32位代码段),地址偏移是一个双字。
  • 特别注意,子程序的嵌入汇编代码应该安排在C函数的return语句之后,以免不经过调用直接进入子程序。

参数传递

方法:

  1. 寄存器传递法
  1. 把参数放在约定的寄存器中
  2. 由于寄存器数量有限,只适用于传递少量参数的情形
  1. 堆栈传递法
  1. C语言的函数通常利用堆栈传递入口参数,而利用寄存器传递出口参数
  2. 用堆栈传递入口参数时,主程序在调用子程序之前,把需要传递的参数依次压入堆栈,然后子程序从堆栈中取入口参数
  3. 有时候需要考虑保护寄存器
  1. 约定内存单元传递法、CALL后续区传递法等

局部变量

局部变量放堆栈中,EBP指向堆栈,能够以EBP作为基址寄存器来访问局部变量。

算术逻辑运算指令

乘除运算指令

有时操作数是隐含的

MUL OPRD

  1. 无符号数乘法指令,另一个操作数隐含,位于寄存器AL,AX,EAX中
  2. OPRD可以是通用寄存器、存储单元,但不能是立即数!
  3. 乘积的高半部分不等于0,则CF=1,OF=1(表示高位含有结果的有效数呗);否则都为0。对其他状态标志无定义

IMUL OPRD

IMUL DEST(只能是通用寄存器),SRC(可以是立即数,尺寸不能超过目的操作数)

IMUL DEST(只能是通用寄存器),SRC1(不能为立即数),SRC2(只能为立即数)

  1. 有符号数乘法指令
  2. 双数或三数,乘积有可能溢出,那么高位部分将被截掉
  3. 单数和无符号乘法指令一样,高位含有效位,则CF=OF=1;双数和三数,溢出高位截断则CF=OF=1。对其他状态标志无定义

DIV OPRD

  1. 无符号数除法指令
  2. 和mul相反,被除数位于AX,DX:AX,EDX:EAX
  3. 字节操作数:AX除以OPRD,商送到AL,余数送到AH
  4. 字操作数:DX:AX除以OPRD,商送到AX,余数送到DX
  5. 双字操作数:EDX:EAX除以OPRD,商送到EAX,余数送到EDX
  6. OPRD不能是立即数
  7. 对状态标志的影响无定义
  8. 除数为0或商太大,则引起除法出错异常

IDIV OPRD

  1. 有符号数除法指令
  2. 被除数位于和DIV一样,商和余数也都和DIV一样
  3. 如果不能整除,余数的符号与被除数一致,而且余数的绝对值小于除数的绝对值
  4. 对状态标志无影响
  5. 有时需要在除操作之前扩展被除数

符号扩展指令

CBW

  1. 字节→字
  2. AL符号扩展到AH

CWD

  1. 字→双字
  2. AX符号扩展到DX

CWDE

  1. 字→双字
  2. AX符号扩展到EAX的高16位

CDQ

  1. 双字→四字
  2. EAX符号扩展到EDX

  1. 四条符号扩展指令均不影响各状态标志。
  2. 在无符号数除操作之前,不宜利用CBW、CWD、CDQ指令来扩展符号位,一般采用逻辑异或XOR指令把高位直接清零(即无符号扩展)

扩展传送指令

MOVSX DEST,SRC

  1. 符号扩展传送指令,扩展SRC的符号后送至DEST
  2. SRC可以是通用寄存器或存储单元,但DEST只能是通用寄存器,且尺寸必须大于SRC
  3. 不改变源操作数,也不影响状态标志

MOVZX DEST,SRC

  1. 零扩展传送指令
  2. SRC、DEST要求同上,也两不

逻辑运算指令

  1. 按位进行
  2. 目的操作数:只能是通用寄存器或存储单元,用于存放运算结果
  3. 如果只有一个操作数,则该操作数既是源又是目的
  4. 两个操作数,最多只能有一个存储单元。操作数们尺寸必须一致。源操作数可以是立即数

NOT OPRD

  1. 将OPRD按位取反
  2. 对标志无影响

AND DEST,SRC

  1. 与运算
  2. 使CF=OF=0,PF、ZF、SF反应运算结果,AF无定义
  3. 操作数自己与自己and,值不变,可以使CF清零
  4. 常用于使操作数若干位不变,另外若干位清零的场合

OR DEST,SRC

  1. 或运算
  2. 使CF=OF=0,PF、ZF、SF反应运算结果,AF无定义

XOR DEST,SRC

  1. 异或运算
  2. 使CF=OF=0,PF、ZF、SF反应运算结果,AF无定义
  3. 自己与自己异或,结果总为0。经常会采用xor把寄存器清零

TEST DEST,SRC

  1. 和and类似,但结果不送到dest,仅影响标志位,使CF=OF=0,PF、ZF、SF反应运算结果,AF无定义
  2. 通常用于检测某些位是否为1,但又不希望改变操作数值的场合

一般移位指令

移位指令需要标明移动的位数,可以是8位立即数,也可以是CL。通过截取count的第五位,实际的移位数被限制于0~31之间。

SAL OPRD,count              //算数左移指令(同逻辑左移)

SHL OPRD,count             //逻辑左移指令(同算数左移)

SAR OPRD,count              //算数右移指令

SHR OPRD,count             //逻辑右移指令

  1. CF受影响,SF、ZF、PF反应移位后的结果,OF很复杂,AF未定义
  2. 左移都是一样的,每向左移动一位,右边用0补足,最高位进入CF
  3. 算数右移左边的符号位保持不变,最低位进入CF
  4. 逻辑右移左边用0补足,最低位进入CF

循环移位指令

ROL OPRD,count             //左循环移位指令

ROR OPRD,count             //右循环移位指令

RCL OPRD,count             //带进位左循环移位指令

RCR OPRD,count             //带进位右循环移位指令

  1. CF受影响, OF很复杂,其他状态标志不受影响
  2. 前两条没有把CF包含在循环的环中,CF和一般循环的变化一样
  3. 每移动一位,CF都会变
  4. 循环移位指令,方便实现一个操作数内部的移位
  5. 带进位循环指令,能够实现跨操作数之间的移位。(将CF作为中间桥梁)

双精度移位指令

老师说不建议使用

SHLD OPRD1,OPRD2,count

SHRD OPRD1,OPRD2,count

  1. OPRD1作为目的操作数可以是通用寄存器或存储单元,字或双字
  2. OPRD2只能是寄存器。操作数尺寸一致。
  3. count都同理,如果超过操作数的尺寸,则目的操作数的结果无定义,状态标志也无定义
  4. 对目的操作数操作,左移把OPRD2放右边,右移把OPRD2放左边
  5. 仅移动一位,若符号位发生变化,则置OF,否则清OF;SF、ZF、PF反应移位后的结果,AF未定义

分支程序设计

  1. 利用条件转移指令和无条件转移指令实现分支
  2. 减少转移(在判断之前先假设满足简单的情形)充分利用寄存器(可以把寄存器作为形参变量之类的)等,都有助于提高执行的效率。

无条件和条件转移指令

(简单转移指令见此处)

条件转移指令和循环指令只能实现段内转移,无条件转移指令和过程调用返回指令都可以实现,软中断指令和中断指令一定是段间转移。

又分为直接转移和间接转移。

JMP LABEL

  1. 无条件段内直接转移指令机器码:操作码OP+地址差rel——地址差是转移目标地址偏移与紧随JMP指令的下一条指令的地址偏移之间的差值。执行无条件段内直接转移指令时,实际上时rel+EIP——相对转移

JMP OPRD

  1. 无条件段内间接转移:在保护方式下,OPRD是32位通用寄存器或双字存储单元,其内容直接被装入EIP

多路分支的实现

  1. switch
  2. 当多路分支在5路以上时,可以考虑通过无条件间接转移指令和目标地址表实现

循环程序设计

循环=初始化+循环体+调整+控制(计数控制法和条件控制法)

循环指令

LOOP LABEL

  1. =DEC ECX ; JNZ LABEL
  2. 段内转移,相对转移,转移范围-128~+127
  3. 自动以ECX作为循环计数器
  4. 不影响各状态标志
  5. 先要设置好ECX的初值
  6. 执行时,首先使ECX减1(不影响标志!),再判断是否为0,所以循环最多可进行232次

LOOPE LABEL         //等于循环指令

LOOPZ LABEL          //全零循环指令

还要看ZF等于1,则跳转

LOOPNE LABEL             //不等于循环指令

LOOPNZ LABEL              //非全零循环指令

还要看ZF等于0,则跳转

计数器转移指令

JECXZ LABEL

  1. 当寄存器ECX=0时,转移到LABEL(即循环次数为0,不进行循环)
  2. 通常在循环体开始之前使用该指令,当循环次数为0时,就可以跳过循环体

多重循环程序

实践过后再说吧。

子程序设计

  1. 必须遵循与主程序的约定
  2. 注意传递参数的方法、安排全局变量的方式、保护寄存器的约定、表述子程序的说明

传递参数方法

  1. 希望利用寄存器传递入口参数:在C源程序中明确_fastcall的调用规定
  2. 给动态局部变量分配寄存器,能有效地提高执行效率

安排局部变量地方法

  1. 利用堆栈来安排局部变量

保护寄存器的约定

  1. 函数代码虽然使用寄存器eax、ecx、edx,但并不事先保护他们;
  2. 函数代码只要使用了寄存器ebx、esi、edi、ebp,总是先保护它们,过后再恢复(这就是默契)
  3. 函数代码对堆栈指针ESP的使用是极其谨慎的
  4. 保护和恢复的时候一定要注意堆栈平衡
这篇关于《汇编语言》chapter3 note的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!