拓展:汇编语言的子程序

一个近(near)调用的程序

  一个简单的包含子程序的汇编程序是:

; 要设置栈段,以便于call和ret指令使用
assume cs:code, ss:stack
stack segment
       db  16 dup (0)
stack ends
code segment
start: mov ax,stack
       mov ss,ax
       mov sp,16
       mov ax,1000
       call s        ;调用子程序
       mov ax,4c00h
       int 21h
    s: add ax,ax    ;子程序开始
       ret       ;子程序返回
code ends
end start

  编译、连接后,用debug观察到:

  从call对应的机器指令中,可以看到这是一种近(near)调用,机器指令EB0500中可以取出要调用的子程序,其偏移地址的位移是0005
  进一步,用t命令,可以观察在调用子程序时,栈的变化过程,从而深刻理解子程序的机理。

一个远(far)调用的程序

assume cs:code, ss:stack
stack segment
       db  16 dup (0)
stack ends
code segment
start: mov ax,stack
       mov ss,ax
       mov sp,16
       mov ax,1000
       call far ptr s  ;在这儿变为远调用
       mov ax,4c00h
       int 21h
    s: add ax,ax
       ret
code ends
end start

  编译、连接,用debug载入后,我们观察:

  这就是远调用!在机器指令中,直接指定了子程序的CS和IP。
  继续单步执行,观察在调用过程中栈的变化。这个观察,对我们了解程序设计中的子程序机制非常重要。

提高程序的可读性

  下面,要将程序变个样。话从何说起呢?我怀念C语言中的{}了。将一段逻辑上相关的代码,放在{}中,看起来就有边有沿的,整齐,带来的好处,可读性提高,更关键的好处,程序的可读性提高。
  于是有了下面的写法。

assume cs:code, ss:stack
stack segment
       db  16 dup (0)
stack ends
code segment

main proc     ;这是我们的主程序
start: mov ax,stack
       mov ss,ax
       mov sp,16
       mov ax,1000
       call far ptr s  ;依然用s标识调用的入口
       mov ax,4c00h
       int 21h
main endp

subp proc   ;这个作为子程序
    s: add ax,ax
       ret
subp endp

code ends
end start

  从中看到,一个程序,分为若干个子程序,每个子程序长下面的样子:

名称 proc
  …… ;实现逻辑功能的指令
  (ret)
名称 endp

  最为关键的就是,将一段程序,我们认为是逻辑功能独立的子程序,用两个关键字,procendp,包围成了一个整体。
  子程序的名称,其实质也是代码的地址。如果子程序就是从第一条指令开始,按下面的写法也行:

assume cs:code, ss:stack
stack segment
       db  16 dup (0)
stack ends
code segment

main proc
start: mov ax,stack
       mov ss,ax
       mov sp,16
       mov ax,1000
       call far ptr subp  ;子程序的名称也就是子程序第一条指令的地址
       mov ax,4c00h
       int 21h
main endp

subp proc
    s: add ax,ax
       ret
subp endp

code ends
end start

冥冥中,我看到我C中的:

int main()
{
      ...
      subp();
      ...
}
void subp()
{
     ...
}

  模块化的味道出来了吧?
  我们更进一步!

汇编程序的多文件组织

  有人说,汇编只能编小程序。
  我替我汇说:不服!
  当编大程序时,分模块做就行了。更关键的,从工程组织的角度,高级语言能够将代码分别写在多个文件中,汇编语言照样能这么干!
  怕有人郁闷,我悄悄地告诉大家,这一招,高级语言是从汇编语言处学的。其实,编程技术都是相通的,大家不要搞得不像一家人。
  把上面的程序,分在两个文件中,一个文件中一个子程序:
  step 1:建立“主程序”文件

;保存为p1.asm,这个文件中包括栈定义,以及“主程序”main
extrn subp:far      ;声明在程序中要用到的subp是一个“外部”名称,要作为个far型的地址值
                    ;这个声明必须有,可以上机试,看不加时有何提示
assume cs:code, ss:stack
stack segment stack
       db  16 dup (0)
stack ends
code segment

main proc
start: mov ax,stack
       mov ss,ax
       mov sp,16
       mov ax,1000
       call far ptr subp
       mov ax,4c00h
       int 21h
main endp

code ends
end start

  将p1.asm单独编译:

  强烈建议:将extrn subp:far省略掉,看看会出现什么?
  step 2:建立“子程序”文件

;保存为p2.asm,这个文件中是“子程序”subp的定义
public subp    ;声明subp将作为公共(public)符号,可以被外部访问
                ;试着将这个声明去掉,它不影响编译,但会影响连接(想想,为什么?)
assume cs:code
code segment
subp proc
  s: add ax,ax
     ret
subp endp
code ends
end

   编译p2.asm:
  step 3:连接
  上述的两个.asm经过编译后,产生了两个.obj文件,分别是p1.obj和p2.obj。现在要做的工作,就是把这两个目标文件连接成一个可执行文件。
  用的命令是:

  连接的结果,产生了可执行文件p1.exe。
  同学们,知道“连接”是什么意思了吧?再来多个文件,继续”+”好了。大工程,真的不惧。
  提示:将step 2中的public subp去掉,看看连接中会出现什么问题。进一步思考,在连接中有什么要求
  step 4:运行程序
  驾轻就熟的事情,debug就行。
  
  “子程序”的代码哪去了?
  可以发现,现在只是“主程序”的代码,主程序在076B段,而子程序,从子程序调用的指令看,在076D段。
  继续看:

  呵呵,这就找到了。

总结

  本文用一个很简单的例子,介绍了汇编语言引入子程序后,程序的结构,以及多文件组织的形式。程序简单了些,但道理都在里面呢。
  可以做一个练习,主程序调用子程序玩一玩。
  
【练习】
  编制一个子程序,求y=x4,自变量 x 为字节,应变量y可以在一个字内存放而不溢出 参考解答
  (1)版本1:子程序的参数由寄存器dl提供,返回结果在ax中;
  (2)版本2:子程序不变,主程序中提供如下数据区,在主程序中,循环调用子程序,完成y=x4的求解,并将结果存入在相应的数据区:

data segment
     x db 1,2,3,4,5,6,7,8
     y dw 0,0,0,0,0,0,0,0
data ends

  (3)版本3:数据区不变,子程序完成全部8个数据的求解任务,主程序只调用一次子程序即可。数据x的起始偏移地址由si提供,存放结果的y的偏移地址,由di提供,在调用前,由主程序为子程序提供si、di值。
  (4)版本4:将上面的程序按多文件的方式存放。

时间: 2025-01-18 23:06:39

拓展:汇编语言的子程序的相关文章

简明x86汇编语言教程(6)

4.0 利用子程序与中断 已经掌握了汇编语言?没错,你现在已经可以去破译别人代码中的秘密.然而,我们还有一件重要的东西没有提到,那就是自程序和中断.这两件东西是如此的重要,以至于你的程序几乎不可能离开它们. 4.1 子程序 在高级语言中我们经常要用到子程序.高级语言中,子程序是如此的神奇,我们能够定义和主程序,或其他子程序一样的变量名,而访问不同的变量,并且,还不和程序的其他部分相冲突. 然而遗憾的是,这种"优势"在汇编语言中是不存在的. 汇编语言并不注重如何减轻程序员的负担:相反,汇

如何在汇编语言中调用c语言标准库函数sin()

问题描述 如何在汇编语言中调用c语言标准库函数sin() ⑴ 编写一个汇编语言递归子程序(子模块)求斐波那契数列,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n>=2,n∈N*):(输入一个数字,若该数字在数列中,则输出N,若不在数列中,输出"error") ⑵ 编写一个计算Z=sin(2n+1)*F(n)的汇编语言主模块,其中sin(x)调用C语言的函数库.F(n)通过调用⑴中的模块实现,输入n.显示格式为: Z= 使

Franklin C51和A51函数的相互调用

1 引言 C语言是一种编译型程序设计语言,它兼顾了多种高级语言的特点,并可以调用汇编语言的子程序.用C语言设计开发微控制器程序已成为一种必然的趋势.Franklin C51是一种专门针对Intel 8051系列微处理器的C开发工具,它提供了丰富的库函数,具有很强的数据处理能力,编程中对8051寄存器和存储器的分配均由编译器自动管理,因而,通常用C51来编写主程序.然而,有时也需要在C程序中调用一些用汇编A51编写的子程序.例如,以前用汇编语言编写的子程序.要求较高的处理速度而必须用更简练的汇编语

汇编设计-汇编语言子程序编程求代码

问题描述 汇编语言子程序编程求代码 目的:掌握子程序的定义指令 字符分类存储并统计每种字符个数COUNT-CHAR;程序接收用户键入的一行字符(字符个数不超过80个,该字符串用回车符结束),并按字母.数字.及其他字符分类计数,然后将结果存入以letter,digit和other为名的存储单元中,其中第一个单元存放该类字符个数,从第二单元开始存放分类的字符. 提示:程序可采用0AH功能调用把键入字符直接送到缓冲区,然后逐个取出分类计数.也可采用01H功能调用在接收字符后先分类计数然后再存入缓冲区.

字符串-汇编语言子程序的调用与返回

问题描述 汇编语言子程序的调用与返回 定义一个子程序:回车换行,主程序:从键盘接收一个字符串,调用子程序,继续从键盘接收一个字符,调用子程序,把输入的字符串用第二次输入的字符截断,显示截断后的结果. 解决方案 1.用call语句调用:2.用USR函数调用 解决方案二: 汇编语言子程序调用call和ret---------------------- 解决方案三: http://www.cnblogs.com/hustlijian/archive/2011/06/06/2073965.html

汇编语言课程主页

[写在开课前] 这学期,接了一门新课--汇编语言程序设计. 这是门好课.学好了,能干的工作,那是高大上:职业中不指望自己的工具袋中备一把名叫"汇编"的榔头的同学,借着汇编语言,也可以练计算机专业学习的内功. 新培养方案中恢复了这门课,我就想将捣鼓捣鼓这门课程.搞了好多年"计算"的事,早对自己离"计算机"有点远而不满.备课中,再回计算机的底层,重新体会"螺丝壳里做道场"的精致. 教学模式继续翻转课堂,随着授课进度,同步建设资源.

新手必看-汇编语言超浓缩教程

" 哎哟,哥们儿,还捣鼓汇编呢?那东西没用,兄弟用VB"钓"一个API就够你忙活个十天半月的,还不一定搞出来."此君之言倒也不虚,那吾等还有无必要研他一究呢?(废话,当然有啦!要不然你写这篇文章干嘛.)别急,别急,让我把这个中原委慢慢道来:一.所有电脑语言写出的程序运行时在内存中都以机器码方式存储,机器码可以被比较准确的翻译成汇编语言,这是因为汇编语言兼容性最好,故几乎所有跟踪.调试工具(包括WIN95/98下)都是以汇编示人的,如果阁下对CRACK颇感兴趣--:二

定时器时间数据转换子程序分析

近日,我在看一时钟TSR程序,分析其中一个把BIOS数据区中的定时器数据转换成HH:MM:SS时间格式的数据(ASCII)的程序片段时,既学到一点东西,又发现了一写自己弄不明白的问题.现在我把自己学到的好东西写出来与大家分享,同时向各路高手请教请教. 程序片段如下:(汇编语言程序) ... (省略) HMS db 8 dup(':')...time proc lea di,position ;(1) --行号mov ax,0 ;(2)mov ds,ax ;(3)mov al,ds:[46eh]

汇编语言命令参数程序的编写

一. 引言: 如果大家用过TurboC2.0/3.0 or BorlandC3.X等编译器编写DOS应用程序的话,编写一个命令行参数形式的应用程序对大家来说是一件非常容易的事情,只要在主函main()中加几个参数就OK(int main(int argc,char *argv[],char *env[]){}).相对汇编语言来说编写一个命令行参数的程序就比较艰难,它要用到DOS的程序段前缀PSP(Program Segment Prefix)知识以及其他相关DOS知识.(本文只对参数介绍,环境块