拓展:汇编语言的子程序

简介: 一个近(near)调用的程序  一个简单的包含子程序的汇编程序是:; 要设置栈段,以便于call和ret指令使用assume cs:code, ss:stackstack segment db 16 dup (0)stack endscode segmentstart: mov ax,stack mov ss,

一个近(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:将上面的程序按多文件的方式存放。

目录
相关文章
|
1月前
|
C语言 数据安全/隐私保护 C++
嵌入式中如何把C++代码改写成C语言代码
嵌入式中如何把C++代码改写成C语言代码
31 0
|
3月前
|
编译器 Serverless C++
从汇编角度看函数调用过程
从汇编角度看函数调用过程
|
存储
汇编语言中“$”的作用
汇编语言中“$”的作用
722 0
汇编语言中“$”的作用
|
8月前
|
存储 安全 编译器
5.10 汇编语言:汇编过程与结构
过程的实现离不开堆栈的应用,堆栈是一种后进先出`(LIFO)`的数据结构,最后压入栈的值总是最先被弹出,而新数值在执行压栈时总是被压入到栈的最顶端,栈主要功能是暂时存放数据和地址,通常用来保护断点和现场。栈是由`CPU`管理的线性内存数组,它使用两个寄存器`(SS和ESP)`来保存栈的状态,SS寄存器存放段选择符,而ESP寄存器的值通常是指向特定位置的一个32位偏移值,我们很少需要直接操作ESP寄存器,相反的ESP寄存器总是由`CALL,RET,PUSH,POP`等这类指令间接性的修改。
41 0
|
3月前
|
存储 NoSQL 编译器
C语言的本质(二):汇编与C
C语言的本质(二):汇编与C
20 0
|
11月前
|
存储 编译器 C语言
【C语言】汇编角度剖析函数调用的整个过程
【C语言】汇编角度剖析函数调用的整个过程
|
存储
汇编语言与微机原理-变量的调试与分析
汇编语言与微机原理-变量的调试与分析
125 0
汇编语言与微机原理-变量的调试与分析
西门子S7-1200基本位逻辑指令编程实例,如何编写起保停控制程序
今天我们来学习一个西门子S7-1200基本位逻辑指令的编程实例,给大家介绍一下如何编写起保停控制程序。
西门子S7-1200基本位逻辑指令编程实例,如何编写起保停控制程序
|
Windows API
16位和32位的80X86汇编语言的区别
需要注意的是汇编不是一种语言,不同平台有不同的汇编语言对应,因为汇编和操作系统平台相关,所以汇编语言没有移植性。对于IA-32架构平台而言,选用的32位80386汇编语言,也就只说讨论的操作系统平台是32位的,可以执行文件的格式也是32位而不是64位或16位的。
1277 0