MASM中子程序(Procedures)的写法
在MASM 5(Microsoft Micro Assembler)的汇编体系中,子程序(Procedures)的定义和调用是非常重要的,就像C、pascal等的函数和方法一样;且对深入理解高级语言里函数的底层原理极其重要,如函数的参数传递、栈、变长参数等。但在网络上许多教程及代码都极其不规范且语焉不详;有的代码虽然可以运行,但都存在潜在Bug。我仔细阅读了Microsoft的相关手册后,像子程序的严格定义及调用分享如下:
一、子程序(Procedure)的定义和调用
方式1
定义:
label Proc [Far | Near]
...(语句)
Ret [n]
label Endp
调用:
Call label
方式2
定义:
label:
...(语句)
Retn [n]
调用:
Call Near Ptr label
方式3
label LABEL far(这里2个label不要搞混了,前一个是子程序名称,后一个是关键字)
...(语句)
Retf [n]
调用:
Call Far Ptr label
注意事项:
1、方式1是官方推荐的,Call时不用关心near(段内调用)和far(跨段调用),编译器会根据定义自动生成near call或far call。但forward-referenced,如果proc定义为far,就必须call far ptr。
2、方式2和方式3定义中Retn和Retf一定要与Call Near Ptr和Call Far Ptr对应,否则栈数据会被破坏
3、从MASM 6后还可通过Proc的定义中声明参数(相当于C语言中函数的定义)。
4、ret、retn、retf后面的n(整数)表示调用返回后sp还需要加n(即还需要弹出n个字节),主要用于传递参数。
二、Call的语法补充
语法:Call oprd(操作数),可分为直接调用或间接调用
1、直接调用
oprd为子程序的地址,则可通过call [near或far]调用,本文之前的调用都是直接调用,英文label编译后就会替换成地址。
2、间接调用
oprd为保存子程序的地址的寄存器或内存地址:
段内调用(near call)则可通过call bx或call word ptr [bx+si+20];
跨段调用(far call)则可通过call dword ptr [bx+si+20];
3、Jmp指令
Jmp指令与Call指令的调用方式完全相同,但Jmp有一个Jmp short label,编译器只生成-128~127的偏移量。
这里估计会有读者感到奇怪,为什么要有这么多方式了,全部改成segment:offset的方式不就行了么,jmp far跳转segment改变,jmp near跳转segment不变,完全没必要有其他方式了,masm还要弄一个jmp short。
这里只要看一下编译器生成的汇编代码便可理解,早期计算机内存空间极其宝贵,生成的代码应尽量简短。short生成的代码比near更短,同理far。即,实现相同的功能,运行代码所需内存空间能少则少,这种理念,在嵌入式开发中普遍应用。
三、同段调用和跨段调用并存
代码调用存在near和far,如果某个proc,同段调用为near,跨段调用为far,那这个proc到底定义成near还是far呢?
这里就涉及代码的组织问题,尽量将程序放在同段。如果条件不允许,只能将部分proc定义到另一个段中。call near只是为了提升执行速度,实在不行,把所有pro都定义为far,同段的子程序也可以用far调用。
JMP也有同样的问题,即使是同段也可以jmp far ptr labelName。