You are on page 1of 235

IBM S/390 汇编语言程序设计

浙江大学出版社
内容介绍
本书以 IBM S/390 大型计算机为背景,介绍了汇编语言程序设计的基本概念、基本原理
和一般方法。其主要内容有:S/390 的地位和作用、S/390 的体系结构和硬件特征、程序设
计的一般概念、汇编语言与汇编程序、顺序与分支程序设计、循环程序设计、子程序、十进
制运算、宏汇编等等。另外,本书还介绍了汇编语言程序设计的一些基本方法和技巧。
本书可作为高等学校计算机系应用数据处理专业(大型机专业)的教材和教学参考书,
也可供 IBM S/390 机的维护人员和有关技术人员阅读参考。

责任编辑:X X
前言

1997 年夏天,教育部(原国家教委)与美国国际商用机器公司(IBM)开始了一投资
超过 3000 万美金的教育合作项目,IBM 向中国四所重点大学(北京大学、复旦大学、华中
理工大学、华南理工大学)捐赠四台 IBM 最先进的大型服务器系统 S/390,教育部在这四所
高校的计算机系开设一个新的专业(方向)——应用数据处理(大型机)。本书即是根据此
专业(方向)的教学计划与教学大纲编写而成的。
本书以 IBM S/390 大型计算机为背景,以 S/390 汇编语言为工具介绍了汇编语言程序设
计的基本概念、基本原理和一般方法。其内容包括:S/390 概述、S/390 汇编语言、程序设
计的一般概念、顺序与分支程序、循环程序、子程序、十进制运算、宏汇编等等。为了使读
者更快地掌握程序设计的方法和更方便地进行程序调试,本书中采用了 ASSIST 汇编程序提
供的一些宏指令。
全书共分为十章。第一章为绪论,介绍了 S/390 计算机系统的发展、地位和作用,以及
汇编语言的特点。第二章介绍了学习汇编语言需要了解的一些基本知识,如 S/390 的体系结
构、寻址方式、指令类型与格式,汇编语言的数据和基本成分等。第三章介绍了汇编程序工
作的概念以及汇编控制指令。第四章介绍了顺序与分支程序设计的概念和方法。第五章介绍
循环程序和内部子程序设计。第六章为综合程序设计小结。第七章介绍外部子程序设计。第
八章介绍十进制运算。第九章介绍一组有用的高级指令。第十章简单地探讨了宏和条件汇编
的概念和方法。在各章之后,均有一定数量的习题,可供读者练习以便掌握本书的内容。附
录为 IBM S/390 系统和汇编语言的有关资料,可供读者查阅。
本书在编写上是以程序设计为导向的,在介绍完 S/390 的基本概念和硬件特征以及汇编
语言的基本概念之后,没有专门的章节完整地介绍指令系统,而是直接进入程序设计阶段,
根据程序的需要介绍有关的指令,这样做的目的是为了让读者能更快更好地掌握程序设计方
法。
本书可作为高等学校计算机系应用数据处理专业(大型机专业)的教材和教学参考书,
也可供 IBM S/390 机的维护人员和有关技术人员阅读参考。
本书吸取了我们多年来在汇编语言程序设计方面的工作、教学实践的一些体会和成果,
并曾以讲义的形式在华南理工大学计算系工程与科学系九五、九六、九七级学生中讲授过。
在编写过程中参考了 IBM 的技术资讯、国外有关教材和国内兄弟院校的有关资料,并得到
了华中理工大学、复旦大学计算机系以及 IBM 中国公司大学合作部的全力支持和帮助,在
此表示衷心的感谢。
由于我们的水平有限和时间比较仓促,书中的错误和不妥之处在所难免,敬请读者批评
指正,不胜感激。

编者
2000 年 2 月于华南理工大学
目 录

内容介绍................................................................. - 2 -
前言 .................................................................... - 3 -
第一章 绪论.............................................................. - 1 -
第一节 IBM 大型机的历史及发展 ................................................................................... - 1 -
第二节 S/390 的地位和作用 ............................................................................................. - 2 -
1.计算机系统的种类及特点.................................................................................... - 2 -
2.S/390 的地位和作用 ............................................................................................. - 2 -
第三节 汇编语言的特点.................................................................................................... - 3 -
1.汇编语言的特点.................................................................................................... - 3 -
2.掌握机器特性的必要性........................................................................................ - 3 -
3.汇编语言进行程序设计的一般过程.................................................................... - 4 -
习题一.................................................................................................................................. - 5 -
第二章 准备知识.......................................................... - 6 -
第一节 S/390 体系结构及硬件特征 ................................................................................. - 6 -
1.中央处理器............................................................................................................ - 6 -
2.主存储器................................................................................................................ - 8 -
3.扩充存储器............................................................................................................ - 9 -
4.通道子系统............................................................................................................ - 9 -
5.辅助存储器............................................................................................................ - 9 -
6.系统构造................................................................................................................ - 9 -
第二节 S/390 的寻址方式 ............................................................................................... - 10 -
1.寄存器寻址方式.................................................................................................. - 10 -
2.立即数寻址方式.................................................................................................. - 10 -
3.存储器寻址方式.................................................................................................. - 10 -
第三节 S/390 指令的格式与类型 ................................................................................... - 12 -
1.S/390 指令格式 ................................................................................................... - 12 -
2.S/390 指令类型 ................................................................................................... - 13 -
第四节 S/390 汇编语言的数据表示 ............................................................................... - 13 -
1.字符数据.............................................................................................................. - 14 -
2.二进制数.............................................................................................................. - 14 -
3.逻辑数.................................................................................................................. - 14 -
4.十进制数.............................................................................................................. - 15 -
第五节 S/390 汇编语言的基本成分 ............................................................................... - 15 -
1.基本字符集.......................................................................................................... - 16 -
2.符号名.................................................................................................................. - 16 -
3.项及表达式.......................................................................................................... - 16 -
4.汇编语句的分类和一般格式.............................................................................. - 17 -
5.机器指令的符号形式.......................................................................................... - 18 -
6.汇编语言程序的书写格式.................................................................................. - 19 -
习题二................................................................................................................................ - 20 -
第三章 汇编程序和汇编控制语句........................................... - 21 -

I
第一节 汇编程序 Assembler............................................................................................ - 21 -
第二节 汇编控制语句...................................................................................................... - 22 -
1.数据与数据区域的定义...................................................................................... - 23 -
2.程序的构成控制.................................................................................................. - 28 -
3.地址的指定.......................................................................................................... - 31 -
4.程序间的结合...................................................................................................... - 33 -
5.符号等价语句 EQU............................................................................................. - 34 -
6.产生符号表的 LTORG 语句 ............................................................................... - 34 -
7.拷贝语句 COPY .................................................................................................. - 35 -
8.打印控制.............................................................................................................. - 35 -
习题三................................................................................................................................ - 36 -
第四章 顺序与分支程序设计............................................... - 38 -
第一节 程序的功能.......................................................................................................... - 38 -
第二节 几条简单指令介绍.............................................................................................. - 38 -
第三节 完整的程序示例.................................................................................................. - 41 -
第四节 程序的改进.......................................................................................................... - 42 -
第五节 条件码的设置与分支程序设计.......................................................................... - 44 -
第六节 数据的输入和转换.............................................................................................. - 46 -
第七节 数据的输出及转换.............................................................................................. - 49 -
第八节 更多的指令和程序示例...................................................................................... - 51 -
第九节 乘法和除法.......................................................................................................... - 59 -
第十节 寄存器等价和扩展助记符.................................................................................. - 63 -
第十一节 符号常数的使用.............................................................................................. - 64 -
第十二节 取地址指令的使用.......................................................................................... - 67 -
习题四................................................................................................................................ - 70 -
第五章 循环程序及内部子程序设计......................................... - 77 -
第一节 循环程序设计...................................................................................................... - 77 -
1. 计数循环指令 BCT .............................................................................................. - 78 -
2. 计数循环寄存器指令 BCTR ............................................................................... - 79 -
3. 小于等于循环指令 BXLE ................................................................................... - 84 -
4. 大于循环指令 BXH ............................................................................................. - 85 -
第二节 内部子程序设计.................................................................................................. - 86 -
1.分支连接指令 BAL............................................................................................. - 87 -
2.分支连接寄存器指令 BALR .............................................................................. - 94 -
3.多寄存器保存指令 STM..................................................................................... - 95 -
4.多寄存器恢复指令 LM....................................................................................... - 95 -
5.A 型地址常量...................................................................................................... - 96 -
习题五................................................................................................................................ - 97 -
第六章 综合程序设计.................................................... - 102 -
第一节 程序文档的标准................................................................................................ - 102 -
第二节 存储器-立即数类(SI)指令示例 ................................................................... - 103 -
第三节 存储器-存储器类(SS)指令示例 .................................................................. - 105 -
第四节 地址计数器的访问............................................................................................ - 107 -
1.引用地址计数器的值........................................................................................ - 107 -

II
2.控制地址计数器的值........................................................................................ - 108 -
第五节 逻辑运算和比较指令........................................................................................ - 117 -
习题六.............................................................................................................................. - 119 -
第七章 外部子程序设计.................................................. - 125 -
第一节 外部子程序介绍................................................................................................ - 125 -
第二节 外部子程序设计................................................................................................ - 126 -
1.外部子程序的结构和调用方式........................................................................ - 126 -
2.外部子程序的编写细节.................................................................................... - 127 -
3.小结.................................................................................................................... - 136 -
第三节 与高级语言之间的连接.................................................................................... - 137 -
1.从高级语言调用汇编子程序............................................................................ - 137 -
2.从汇编语言调用高级语言子程序.................................................................... - 137 -
第四节 虚拟段的使用.................................................................................................... - 138 -
习题七.............................................................................................................................. - 143 -
第八章 十进制运算...................................................... - 146 -
第一节 压缩和解压指令................................................................................................ - 146 -
第二节 十进制加减运算................................................................................................ - 149 -
第三节 十进制乘除运算................................................................................................ - 151 -
第四节 输出编辑............................................................................................................ - 152 -
第五节 十进制移位操作................................................................................................ - 168 -
第六节 十进制与二进制之间的转换............................................................................ - 169 -
习题八.............................................................................................................................. - 170 -
第九章 高级指令介绍.................................................... - 175 -
第一节 逻辑运算指令.................................................................................................... - 175 -
第二节 屏蔽测试运算.................................................................................................... - 181 -
第三节 移位操作............................................................................................................ - 182 -
第四节 半字指令............................................................................................................ - 189 -
第五节 MVCL、CLCL 和 EX 指令.............................................................................. - 194 -
第六节 TR 指令.............................................................................................................. - 197 -
第七节 TRT 指令 ........................................................................................................... - 199 -
第八节 CLM、ICM 和 STCM 指令.............................................................................. - 205 -
第九节 SPM 指令........................................................................................................... - 206 -
习题九.............................................................................................................................. - 207 -
第十章 宏及条件汇编.................................................... - 212 -
第一节 符号参数和宏.................................................................................................... - 212 -
第二节 条件汇编............................................................................................................ - 217 -
习题十.............................................................................................................................. - 221 -
附录 A 扩展助记符表 .................................................... - 224 -
附录 B IBM EBCDIC 编码表................................................ - 225 -
附录 C 常用指令表 ...................................................... - 226 -
附录 D 参考文献 ........................................................ - 228 -

III
第一章 绪论

第一节 IBM 大型机的历史及发展

1966 年 4 月,IBM 公司宣布了 System/360(以下简称 S/360)系列计算机,这在计算机


发展史上具有划时代的意义:因为在此之前,所有的计算机都是专门设计和制造的,仅用于
军事、科研等部门,相互之间是不通用的;S/360 是世界上第一台批量生产的通用型计算机,
价格便宜(相对于专用机型而言)而且可以广泛的应用于科学和工程计算、商业及事务处理
等多种领域,所以很快得到了市场的认同,逐渐占据了世界上大、中型通用计算机市场的统
治地位,得到了业界广泛的应用。这之后,IBM 继续不断地改进和发展改型计算机,七十
年代推出了 System/370,八十年代推出了 System/370-XA(Extended Architecture)和 ES9000
(Enterprise System),九十年代推出了 System/390。与此同时,在 S 系列大型机上的操作系
统也从最初的简单单道作业系统发展到了今天世界上规模最大、功能最强、最复杂的大型操
作系统 MVS/OS390。
以下是该系列计算机的发展过程示意图:

发布时间 机器 操作系统

1966 年 System/360 OS360 PCP


↙ ↘
1967 年 System/360 OS360 MFT OS360MVT
↓ ↓
1972 年 System/370 ↓ OS/VS2 SVS
OS/VS1 ↓
1974 年 System/370 OS/VS2 MVS

1978 年 43xx MVS/SE

1980 年 30xx MVS/SP V1
(MVS/370)

1981 年 System/370-XA MVS/SP V2
(MVS/XA)

1988 年 ES9000 MVS/SP V3
(MVS/ESA)

1990 年 9 月 System/390 ↓
OS390

图 1-1 IBM S 系列大型通用计算机的发展过程示意图

-1-
注:MFT: Multiprogramming with a Fixed number of Tasks
MVT: Multiprogramming with a Variable number of Tasks
SVS: Single Virtual Storage
MVS: Multiple Virtual Storage
ESA: Enterprise System Architecture

第二节 S/390 的地位和作用

1.计算机系统的种类及特点
在计算机产业界,一味地肯定或否定大型计算机的作用是不明智的。正如每种程序设计
语言都有自己的特点和应用领域一样,不同的计算机也有自己的特点和应用领域,没有任何
一种单一的计算机系统类型可以适合于所有的应用领域。在今天的计算机产业界,主要存在
着以下几种类型的计算机系统:
①.以 S/390 为代表的大中型计算机系统
这种类型的计算机系统主要的技术特点是:CPU 利用率极高、多任务处理水平
高、密集的 I/O 处理、能很好地处理不同类型的工作负载,并且提供非常高的可靠
性和可提供性。由于这些技术特点,传统上这种类型的计算机系统主要用于处理大
中型企业的商业计算任务。
②.以 Unix 机器为代表的中小型计算机系统
这种类型的计算机系统主要的技术特点是:面向计算,多任务处理水平高、中等
程度的 I/O 处理、在相同类型的工作负载下能很好的处理,可靠性和可提供性一般。
由于这些技术特点,传统上这种类型的计算机系统主要用于工程计算和中小型企业
的商业计算。另外,这种类型的计算机系统经过改良之后的高端类型可用于大规模
的科学计算。
③.以 PC 为代表的微型计算机系统
这种类型的计算机系统主要的技术特点是:面向单个的用户,友好的图形界面,
多任务处理水平低、I/O 处理不密集、可靠性和可提供性较差。传统上这种类型的计
算机系统主要用于与计算机系统的最终用户交互,在这种平台上提供有相当丰富的
应用程序,例如文字处理、电子表格、游戏等。

2.S/390 的地位和作用
传统上,对于企业一级的应用而言,拥有强大处理能力的大型计算机系统的优势是不言
而喻的,但是,该系统也有缺点:主要是体积庞大、价格昂贵、维护费用高昂、软件不够丰
富等等。七十年代末诞生的微型计算机以及更早一些诞生的中小型计算机系统随着其性能在
整个八十年代大大提高、价格大大降低,开始挑战大型计算机系统在企业一级应用的统治地
位。分布式处理、通用平台(Unix)、小型微型化开始在业界流行和普及,在八十年代末大
型计算机系统的地位已经开始岌岌可危,甚至有人提出了“恐龙论”,认为大型计算机系统
是行将灭绝的恐龙,很快将会完全被中小型和微型计算机系统所取代。
真的是这样吗?九十年代计算机工业的发展否定了这一预测,因为在八九十年代这二十
年中,前十年的发展以计算机系统小型微型化为最主要的特征,而后十年的发展则以计算机
系统网络化为特征,在这一趋势的推动下,有三个主要的动因挽救了大型计算机系统的生命
并使之茁壮地成长:

-2-
①.计算机用户方面,随着业务的成长,采用小型微型计算机系统的企业需要不断地增
加新的系统才能适应业务的需要,他们逐渐发现,购置一个庞大的分布式系统所花的投资也
许比同等规模的大型计算机系统要便宜,然而要维护一个庞大的分布式系统所花费的费用却
要远远高于同等规模的大型计算机系统,并且前者的可靠性和可提供性要远低于后者。
②.IBM 方面,在新的公司总裁的领导下,开始了振兴大型计算机系统的努力。首先
从硬件方面着手,采用了小型微型计算机系统常用的 CMOS 技术来制造 CPU 芯片,机器的
发热量大大减少,从而可以采用普通的风冷系统代替价格昂贵的水冷系统,计算机系统的价
格和运行费用因此大大降低;软件方面,在增强传统应用的同时,大型计算机系统开始支持
流行的应用,例如 Unix 和 NT 应用,并开始提供新的功能,例如,支持 Java 开发工具,群
件,Web 服务等等。
③.整个产业界方面,以 Internet、Intranet、Extranet 为代表的计算机网络技术的流行
和普及,推动了对网络服务器的需求。在迅猛增长的网络客户需求的要求下,以小型和微型
计算机系统充当的网络服务器在响应性能、安全性和可靠性方面都已经不能满足需求了。怎
样解决这一问题?一是采用多台小型微型机器组成群集系统,但是群集系统不便于管理,并
且也不大安全可靠;二就是采用大型计算机系统来充当超级服务器,从而很好地解决了管理
和安全性可靠性的问题。
由于以上三个方面的原因,从九十年代中期开始,计算机界开始了所谓“再集中”的发
展趋势,即企业应用重新集中到以 S/390 为代表的大型的服务器当中去。据第三方信息评估
机构的估计,今天全世界 80%以上的信息存储在 IBM 的大型服务器当中。

第三节 汇编语言的特点

1.汇编语言的特点
汇编语言是与机器密切相关的一种程序设计语言,属于第二代程序设计语言。在汇编语
言出现之前,人们使用机器语言(亦即第一代程序设计语言)进行编程。
机器语言使用二进制来代表指令和数据,是计算机裸机能够直接理解的唯一语言。用机
器语言编写程序可以充分利用机器指令的灵活性,达到精确的控制和较高的效率。但是这种
程序使用的二进制编码形式比较难读难写,不便于维护和修改,掌握和使用这种语言的难度
相当大。
为了解决这个问题,人们开始使用汇编语言进行程序设计。汇编语言的主要思想使用简
单易记的符号(例如英文单词)来表示机器指令,即使用“助记符”代替机器指令的操作码,
使用“标识符”代替地址。与机器语言相比,在程序效率牺牲不大的情况下,程序的可读性
和可维护性大大的增强。因此,在低层程序设计领域,汇编语言已经成为最主流的语言。
在高级语言广泛使用的今天,汇编语言的使用仍然是不可缺少的。因为使用汇编语言编
程可以充分利用机器的硬件特性,编写出来的程序代码占用空间小,效率高,所以在编写一
些系统程序时(例如操作系统内核、硬件驱动程序),汇编语言是第一选择。另外,在要求
实时响应和精确控制的工业现场,汇编语言也是得力的工具语言。

2.掌握机器特性的必要性
由于汇编语言适于机器密切相关的一种程序设计语言,因此在进行汇编语言程序设计
时,必须了解机器的有关特性,这样才能编出正确的汇编语言程序。概括起来,我们所要了
解的机器特性有:

-3-
①.存储器的基本单元、容量。机器存储器的基本单元(即编址的基本单位)是什么?
例如字节。以相邻字节为单位进行操作的单元有哪些种类?例如字、半字、双字等。其字长
是多少?存储容量多大(即基本单元数量是多少)?地址表示的方法和地址变换机构(用于
支持虚拟存储器)又是怎样的?
②.控制器的功能及相互关系。必须了解的有:通用寄存器(包括累加器)的数量、功
能,程序状态字(包括状态寄存器和指令地址计数器)与程序设计有关的性能等问题。
③.数据类型和格式。机器能处理哪些类型的数据(例如,定点数、浮点数、十进制数、
字符数据等)?每种数据在机器内部的表示方法(即机内格式)是怎样的?
④.指令格式、长度、功能及寻址方式。对机器指令系统的深入了解是汇编语言程序设
计的重要任务之一,它包括:指令格式是怎样的?指令的长度为何?指令中操作数的地址是
怎样形成的(即寻址方式如何)?指令的种类有哪些?每一条指令的功能如何?程序设计应
注意些什么?等等。
⑤.控制器的其他特性。汇编语言程序设计要了解算术逻辑部件和控制器的状态(这些
信息集中反映在所谓程序状态字 PSW 中)、中断、时钟等。有些机器的控制器允许有多对
互斥状态。例如,目态与管态,等待与运行,停机与共工作等。所 wei 目态就是运行用户程
序(或问题程序)的状态,在此状态下禁止使用特权(或管台)指令;而管态是管理程序运
行的状态,在这宗情况下,可以运行包括特权指令在内的所有指令。中断也是十分重要的特
性,简单说来,中断就是当发生某种紧急或意外事件时,需要算术逻辑部件和控制器停止执
行当前的程序而去处理该事件,处理完后,再去执行被暂时停止执行的程序或转去执行其他
程序。因此,需要了解哪些中断与程序设计有关?中断时的现场保护在什么地方?如何屏蔽
与开放中断?机器的时钟如何利用?等等。
⑥.外部设备的特性。外部设备是计算机与外部世界交换信息的手段。进行汇编语言程
序设计时,必须了解有哪些外部设备可供使用?每种外部设备的具体使用方法如何(包括信
息交换的机构和交换方式)?如何使外部设备与算术逻辑部件和控制器协调工作(包括速度
匹配)?有无通道设施?如何组织通道程序等。

3.汇编语言进行程序设计的一般过程
程序设计包括了多方面的工作,特别是解决复杂问题时情况更是如此。因此,程序设计
一般要经历以下几个阶段:
①.定义问题
这个阶段也称为要求定义分析,或称需求分析,即对要解决的问题的意义和要求,
了解明白准确,这包括制定一系列清晰而无二义性的规格说明。例如问题要求什么样的精度,
提供的输入是什么,以及期望的结果又是什么等等。这个阶段是十分重要的,对复杂问题尤
其如此。因为它产生的规格说明书是以后各阶段的依据。如果不把问题的含义弄明白、准确,
那将会导致整个设计的失败或返工。
②.构造解法概要
这个阶段也称为功能设计。主要是制定整个解法的概要,即总体设计。这包括将整
个问题分解成若干任务或子任务(这主要是按功能划分),以及它们之间相互关系的描述。
目前采用的有功能模块分割法,逐步求精法等。
③.确定算法
这个阶段也称为结构设计。主要是选择最优的算法和数据结构以实现上述的每一个
任务(或子任务)。即根据功能模块而选择最适当的方法和数据结构以实现之。这是程序设
计的核心步骤,也是比较困难的工作。
④.编码

-4-
这个阶段也称为制作程序或编程序。即把算法用此汇编语言书写出来。
⑤.调试和排错
亦称测试。主要是过通测试来保证程序正确。一般要作静态检查和动态检查。静态
检查包括人工检查和上机进行语法检查。动态检查就是根据程序在工作中所有可能的情况
(例如对各种不同初始数据的输入)进行测试,检查执行是否正确。这个阶段的工作量一般
是比较大的。
⑥.整理文档
程序设计的结果包括两大部分,一是程序,二是文档。文档包括各个设计阶段的规
格说明书,以及用户使用手册等。这是用户使用、维护程序的依据。
⑦.维护
这主要是保持程序能正常运行(在用户的整个使用期间)。这包括程序运行中发生
错误后对程序的修改,以及当条件、环境发生变化和增加某些功能时,需要修改规格说明和
相应的程序。这就是说,它包括程序正确性、完善性、适应性的维护。

习题一

1.计算机系统有哪些种类?各有什么特点?
2.S/390 为什么在计算机产业界有着极其重要的地位和作用?
3.为什么要学习汇编语言?
4.学习汇编语言要掌握哪些机器特性?
5.用汇编语言开发应用的过程和步骤是什么?

-5-
第二章 准备知识

第一节 S/390 体系结构及硬件特征

现 代 计 算 机 系 统 的 基 本 结 构 几 乎 都 是 数 学 家 冯 · 诺 依 曼 ( John Von Neumann )


(1903-1957)所归结的“存储程序控制”结构。因此,这种结构也被称为“冯·诺依曼”
计算机体系结构。它的基本工作原理是将编制好的程序存储在计算机的存储器当中,每次从
中取出一条指令,控制器根据该指令的要求控制计算机的各组成部件协调工作,直至程序结
束。尽管计算机种类繁多,功能各异,但是要能计算、判断并自动地进行工作,必须具有:
中央处理器(算术逻辑部件和控制器)、存储器、输入输出设备等最基本的装置。只有对上
述硬件结构及其特征有了深入的理解,我们才能正确进行汇编语言程序设计。
S/390 作为一种通用性的大型计算机系统,也遵循“冯·诺依曼”原理,但是与小型微
型计算机相比,它又有着自己的特征,以下是 S/390 计算机系统的基本组成结构图,我们将
逐一地加以解释:

C S DASD
CPU H U Auxiliary
A B Storage
N S
Expanded Central Tape
N Y
Storage Storage
E S
L T
CPU E Printer
M

Other I/O Devices

注:Expanded Storage:扩充存储器
Central Storage: 主存储器
Auxiliary Storage:辅助存储器
CPU: Central Processing Unit,中央处理器
Channel Subsystem: 通道子系统
DASD: Direct Access Storage Device,直接存取存储设备,即硬盘
Tape:磁带

图 2-1 IBM S/390 计算机系统的基本组成结构图

1. 中央处理器
S/390 主机中允许安装 1 至 10 个 CPU,每个 CPU 都是 32 位字长,
拥有自己独立的 Cache,
独立访问内存,能响应多种内部外部中断,在操作系统的调度下共同协调完成复杂的工作。
每个 CPU 的构造都是相同的,以下以一个 CPU 为例说明他们的组成(仅介绍学习汇编语言

-6-
需要了解的部分):

⑴.寄存器
寄存器主要用于暂时保存 CPU 处理的中间信息,其速度比主存储器要快的多,以
便确保与 CPU 的运算速度相匹配。在 S/390 的 CPU 中拥有多达四组的寄存器组,它们是:
①.控制寄存器 Control Registers(32 位),共有 16 个,编号为 0 到 15。控制寄存
器是一种操作运算的工具,它们用来管理除程序状态字以外的各种控制信息。控制寄存器对
一般的编程人员是透明的。
②.访问寄存器 Access Registers(32 位),共有 16 个,编号为 0 到 15。访问寄存
器是一种地址运算的工具,它们用来帮助计算指令及数据的地址信息,即实现虚地址到实地
址的转换。访问寄存器对一般的编程人员是透明的。
③.通用寄存器 General Registers(32 位),共有 16 个,编号为 0 到 15,并由指令
中的 4 位 R 字段所指定。本书中以后用符号 R0,R1,R2,﹒﹒﹒,R15 代表它们。通用寄
存器顾名思义是一种通用的运算工具,它们用来进行指令计算机其它各种操作,是汇编语言
程序员进行控制的最主要的寄存器组。在算术运算中,通用寄存器还可以用作累加器,对某
些操作,两个相邻的通用寄存器可以连在一起作为一个 64 位的寄存器使用。
④.浮点寄存器 Float Registers(64 位),共有 4 个,编号为 0,2,4,6。浮点寄
存器是一种用于浮点运算的工具,其中既可以放段浮点数(32 位),也可以放长浮点数(64
位)。某些情况下汇编语言程序员也需要访问浮点寄存器组。

⑵.程序状态字 PSW(Program Status Word)


为了便于操作系统对计算机运行进行管理,CPU 必须随时向操作系统提供机器运
行状态。在 S/390CPU 中,IBM 把表示程序运行状态的各种触发器、程序计数器组合在一起,
构成程序状态字寄存器 PSW(Program Status Word),以便 CPU 能集中向操作系统反映机器
的运行状态,并对程序的运行进行控制。程序状态字长 64 位。
CPU 每执行一条指令都可能要修改相应的程序状态字。在汇编程序设计中,可通
过程序状态字得到机器的状态,也可通过改变程序状态字的内容实现对机器运行的控制。程
序状态字的格式如下:

0 R 000 T I E PSW 1 M W P AS CC Program 0000 0000


Key Mask
0 5 6 7 8 11 12 13 14 15 16 17 18 19 20 24 31
A Instruction Address
32 33 63

图 2-2 PSW 格式构造图

其中:
z R(第 1 位) :程序事件纪录(PER)屏蔽位。该位控制 CPU 是否引起与程序事件纪录
有关的中断发生。当 R=0 时,PER 事件不能引起中断;当 R=1 时,允许中断,但中断
是否发生还受控制寄存器 9 中的 PER 事件屏蔽位控制。
z T(第 5 位):当 T=1 时是 DAT 方式。
z I(第 6 位): I/O 中断总屏蔽位。控制 CPU 是否允许 I/O 中断,I=1 时允许 I/O 中断。
z E(第 7 位): 外部中断屏蔽位。控制 CPU 是否允许由外部事件引起的中断。E=1 时允
许外部中断。

-7-
z PSW(第 8~11 位): PSW 存取健。这 4 位构成访问主存的存取健。每当 CPU 访问内存
时,都要用此键码与主存键进行符合比较,从而实现数据保护的目的。
z M(第 13 位): 机器校验屏蔽位。
z W(第 14 位): 等待/运行状态位。当 W=1 时,机器处于等待状态,不执行任何指令;
W=0 时,CPU 处于运行状态。
z P(第 15 位) : 目态/管态位。当 P=1 时,CPU 处于目态,只能执行非特权指令;当 P=0
时,CPU 处于管态,可执行一切特权指令。
z AS(第 16~17 位): 地址空间控制位。规定如下:
xx 实(Real)模式(T=0)
00 基本空间(Primary-space)模式
01 访问寄存器(Access-register)模式
10 第二空间(Secondary-space)模式
11 起始空间(Home-space)模式
z CC(第 18~19 位): 条件码字段。存放执行算术指令、逻辑指令、比较指令和输入输
出指令结果所建立的条件码,条件转移指令将以条件码的值作为是否转移的根据。
z Program Mask(第 20~23)位: 程序中断屏蔽字段。段中的每一位分别对应一个程序例
外事件,某屏蔽位为 1 时,所对应一个例外事件发生将引起一个程序中断。屏蔽位与程
序例外事件的对应关系如下:
第 20 位: 定点溢出
第 21 位: 十进制溢出
第 22 位: 阶码下溢
第 23 位: 说明出错
z A(第 32 位) : 寻址方式位。A=1 是 31 位寻址方式;A=0 是 24 位寻址方式。
z Instruction Address(第 33~63 位): 指令地址字段。该字段指出下一条指令所在的内存
存储单元的地址。

2.主存储器
主存储器,是 S/390 计算机系统中的主要部件,它为系统提供了能直接编址、快速访问
的存储设备。在存储器中存放着要执行的程序和要处理的数据。系统的实存容量随机种和机
型的不同而不同,实存的大小直接影响这系统的性能。上面提到,S/390 最多能处理 31 位
的地址,也就是说主存大小最大为 2G 字节。作为汇编程序员,并不需要了解系统到底安装
了多少实存,因为程序中访问的存储器实虚拟存储器,由系统在执行时将虚拟地址转换为实
存地址。虚存的大小取决于机器指令的寻址空间。
主存的编址单位是字节,在 S/390 中,又规定了半字(双字节)、全字(4 字节)、双字
(8 字节)编址单位。如图 2-3 所示,所以 S/390 的字长为 32 位(4 字节)。另外,对于操
作系统而言,一个长度为 4K 字节的连续主存空间被称为一个页(Page),它是用来与扩充
存储器以及辅助存储器交换的单位。

1000 1001 1002 1003 1004 1005 1006 1007


字节 字节 字节 字节 字节 字节 字节 字节
半字 半字 半字 半字
全字 全字
双字

图 2-3 主存储器编址单位

-8-
一条指令在一个存取周期内存取数据是按编址单位存取,由于各种编址单位在内存中的
地址是以该单位的首字节地址来确定,这就要求各种编址单位的地址按其宽度(字节的整数
倍)存放。这就是所谓的整边界概念。在实际使用的 24 位或 31 位地址中,整边界的原则是:
半字:连续 2 个字节,二进制地址的最后一位为 0
全字:连续 4 个字节,二进制地址的最后二位为 0
双字:连续 8 个字节,二进制地址的最后三位为 0

3.扩充存储器
主存储器受到 31 位地址线的访问限制,最大只能达到 2G 字节,有时为了能进一步地提
高系统的性能,需要对内部存储器的容量进行扩充,扩充存储器顾名思义是对主存储器的扩
充。它也属于内部存储器,但是在速度上要比主存储器慢一些。另外,与主存储器不同的是,
扩充存储器不可以直接在程序中进行访问(因为没有办法写出地址)而必须由操作系统进行
调入主存储器中才可以进行访问。操作系统每次可以把 4K 主存中的内容(称为 1 页 Page)
写入扩充存储器中,也可以从扩充存储器中每次读出 4K 中的内容(称为 1 帧 Frame)写入
主存储器中。操作系统的此调度对程序员是透明的。

4.通道子系统
大型计算机系统的工作负荷十分沉重,既要及时地与用户进行交互,又要保证批处理任
务按时完成。在小型微型计算机系统中,所有的工作都是在 CPU 的控制下进行的,这种方
式对大型计算机系统不再适合,因为系统负荷中的各种任务的性质和响应要求是不同的。为
了减轻 CPU 的负担,提高整个系统的效率,在 S/390 系统中采用了通道子系统来专门处理
输入输出请求。通道子系统实际上是一台专用的计算机,它拥有自己独立的 CPU,能够独
立地在通道程序的控制下完成输入输出工作,与系统的 CPU 一起协调的工作,从而能极大
地提高整个系统的效率。在 S/390 的通道子系统与外部设备之间 IBM 还采用了一种称为
ESCON 的结构,即用光纤把高速外设与通道子系统进行连接以保证即高的数据传输速度,
从而使的整个系统的输入输出速度大大提高。

5.辅助存储器
辅助存储器有时也称为外部存储器或第二存储器(Secondary Storage),这类存储器主要
由直接存取设备,例如磁盘(包括软盘、硬盘、光盘等),顺序存储设备,例如磁带等组成。
机器的内部存储器(包括主存储器、扩充存储器)的优势是访问速度很快,但是也有劣势,
那就是:第一、容量有限;第二、价格昂贵;第三、除了 ROM 之外,在机器断电的情况下
所有存储在其中的信息不能保持。为了解决这些问题,必须在系统中配备辅助存储器。辅助
存储器基本上属于磁记录设备,信息的保存与否与系统是否有电无关;辅助存储器的单位存
储代价要远小于内部存储器,而且辅助存储器的容量几乎是无限的。
在 IBM 的术语中,硬盘设备一般被称为 DASD,即直接访问存储设备。另外,硬盘上
一个长度为 4K 字节的连续空间被称为一个 Slot,它是用来与主存储器交换的单位。

6.系统构造
S/390 的系统构造是非常灵活的,用户可以根据自己的业务需要灵活地选择系统的配置,
并且随着用户业务的成长系统也是可以动态调整以适应需求的。
就单一系统而言,S/390 有以下四种配置方式:
⑴.单 CPU 模式(Uniprocessor),即只有一个 CPU。这样配置的系统适合于系统负荷

-9-
较小且负荷性质相似的情形;
⑵.非分区的多 CPU 模式(Non-Partitioned Multiprocessors),即拥有多个 CPU,在程
序运行时由操作系统进行 CPU 间的任务调度。多个 CPU 共享系统的内存和其它资源。这样
配置的系统适合于系统负荷较大且负荷性质相似的情形;
⑶.物理分区的多 CPU 模式(Partitioned Multiprocessors),即拥有多个 CPU,CPU 被
划分成相互独立的组,内存和其它资源也被划分成相应的组,CPU 和它们对应的资源被称
为分区。在每个分区可以独立运行同一个或不同的操作系统。分区的划分是静态的。这样配
置的系统适合于系统负荷较大且负荷性质相异的情形;
⑷.逻辑分区的多 CPU 模式(Logical Partitioning),即拥有多个 CPU,CPU 被划分成
相互独立的组,内存和其它资源也被划分成相应的组,CPU 和它们对应的资源被称为分区。
在每个分区可以独立运行同一个或不同的操作系统。分区的划分是动态的。这样配置的系统
适合于系统负荷较大且负荷性质相异的情形且效率要高于物理分区模式。
当最高端的单台大型计算机已经不能满足业务需要时,客户可能会选择安装多台机器。
就有多台机器构成的大系统而言,S/390 也有以下四种配置方式:
⑴.松散耦合系统,即多系统之间共享辅助存储资源,如硬盘系统。系统之间是异步的;
⑵.紧密耦合系统,即多系统之间共享存储资源,如,主存储器、硬盘系统。系统之间
是异步的;
⑶.复杂耦合系统(Sysplex,System Complex),即多系统之间可以共享资源,平衡作
业负载,所有者一切都是在同一个时钟的控制下进行,即系统之间是同步的;
⑷.并行复杂耦合系统(Parallel Sysplex),即更为复杂的 Sysplex,系统中可以允许多
达 32 台 S/390 并行同步工作。

第二节 S/390 的寻址方式

“冯·诺依曼”结构的计算机中的所有处理都必须涉及到指令和操作数。因此为了进行
处理,就要找到正确的指令和操作数,寻找指令和操作数的方法,即被称为寻找方式,寻找
指令和寻找操作数相应地被称为指令寻址方式和操作数寻址方式。可以说,寻址式计算机中
最基本,也是最频繁的活动。本节讨论操作数寻址方式。S/390 中操作数寻址方式通常分为
以下三种:
1.寄存器寻址方式
操作数存放在通用寄存器中,从通用寄存器中找到操作数的寻址方式就是寄存器寻
址方式。在寄存器寻址方式中,指令中的寄存器编号给出了操作数的地址。因此,这种寻址
方式可以不访问存储器就能够得到操作数。
2.立即数寻址方式
操作数直接存放在指令当中,取到指令就取到了操作数,这种寻址方式称为立即数
寻址方式。
3.存储器寻址方式
操作数存放在存储器中,从存储器中取出操作数的寻址方式就是存储器寻址方式。
存储器寻址方式比前两种方式要复杂的多。为了能清楚地了解存储器寻址方式的特点,需要
了解两个重要的概念:静态重定位和动态重定位。
通常我们把由程序员编制的程序地址称为逻辑地址或虚地址,而把程序和数据在主
存内的实际地址称为物理地址或实地址。最初的计算机和程序设计概念中,程序员编制的用

- 10 -
户程序(包括数据)在主存中的存放位置是由程序员决定并指明的,即逻辑地址与物理地址
是一致的,因此,由逻辑地址构成的逻辑地址空间(虚拟地址空间或程序空间)和由主存实
地址构成的物理地址空间(实地址空间)也是一致的。这种方式比较适合于运行单道作业的
监控系统(操作系统的前身)的计算机系统。
后来,随着较复杂的操作系统的出现,主存中可以同时运行多道程序。由于主存只
有一个从零地址开始的编制空间,程序员不可能预先知道他所编写的程序是否能从零地址或
其它固定地址开始编址,显然程序中的地址不能与主存地址相符,即逻辑地址与物理地址空
间是不相等的。因此需要由装入程序在程序装入主存时,进行逻辑地址空间到物理地址空间
的变换。如图 2-4 所示。

主存空间
A 程序的逻辑地 0
址空间

0 a
A 程序
转移 S 的物 转移 a+s
取数 D 理地 取数 a+d
S 址空 a+s
D 间 d+s
M m+s

图 2-4 静态重定位示意图

这种变换必然要改变指令的地址码,而且要在程序执行之前把地址空间变换好,在
整个程序的执行过程中,物理地址空间的位置不再动态改变,故这种方法称为静态重定位。
但是,到了六十年代,随着多道程序的广泛应用,一般程序(如常用子程序)可能会被多个
用户程序所公用。这时,若出现修改指令地址不当的情况就不仅会影响到本道程序,还会使
其它道程序也出错。而且,这种指令(包括地址码)允许修改的做法妨碍了程序可重入性的
实现,并对诊断和程序调试到不利。因此,这种修改指令地址码的方法就有危险性并且不方
便。
那么,在不许修改指令地址码的前提下,如何实现逻辑地址空间到物理地址空间的
变换?由图 2-4 看出,就装入程序时所执行的地址空间变换来看,除了把该道程序装入主存
以外,还得给程序的响应地址码加上该道程序在主存中的起始地址(即 a)。既然不准修改
地址码,那么这个相加操作只能透明地实现,这就是广泛采用的所谓基址寻址的办法。

指令地址码 基址寄存器
逻辑(形式)地址 程序基点(a)

地址加法器

物理(有效)地址

图 2-5 动态重定位示意图

- 11 -
如图 2-5 所示,在这种基址寻址方法中,不是以指令地址码(逻辑地址)直接去访问
主存,而需要加上基址寄存器中的程序基点地址(简称基址),形成物理地址后才能去访问
内存。
有了这种基址寻址的支持,装入程序的任务就可简单得多,它只需要控制把程序装入
主存,并把该道程序在主存实际位置的基地址送入相应的基址寄存器,然后每条程序指令的
执行过程中,通过加基地址把逻辑形式地址变换到物理有效地址。相对于前述的静态重定位,
人们把这种在执行每条指令过程中才形成访问内存物理地址的方法称为动态重定位。
例如,假定基地址为 10000,基址寄存器为 R15,则(R15)=10000,逻辑地址为
2400,则物理地址为 10000+2400=12400。
S/390 汇编语言中给出了书写逻辑地址的多种方式,以便提供灵活多样的存储器寻址
方式。指令中的操作数地址码,由形式地址 D、基址地址 B 和变址地址 X 组成,由此形成
了形式地址和有效地址的概念。形式地址,也称位移量或偏移量,它是指令中给出的地址量。
有效地址,即是经过变换后的实际物理地址。
基址地址 B 和变址地址 X 存放在通用寄存器中,可以是 24 位或 31 位的地址码。指
令中用相应的通用寄存器的代号来表示它们,即 4 位长的一个字段,例如 R5 用“0101”表
示。基址寄存器的内容在程序执行过程当中一般不变化,而变址寄存器的内容可以任意变化,
以便形成灵活的寻址方式。形式地址 D 的长度为 12 个二进制位,可以在 0~4095 之间变化。
在采用这种地址表达方式的 S/390 计算机中,地址的一般表达方式为:
X B DDD
其中,X和B分别是十六进制表示的变址寄存器和基址寄存器号(1~15),DDD是三位十六
进制表示的位移量。另外注意,R0不能用作基址或变址寄存器,当指令中在X或B位置给出
“0000”时,系统将予以忽略。

第三节 S/390 指令的格式与类型

根据“冯·诺依曼”计算机工作原理,计算机的工作是受到存储在存储器当中的程序说
控制的,而程序是由一条条的指令所构成,也就是说计算机的操作是受顺序执行的指令所控
制的。S/390 的指令分为机器指令和宏指令。机器指令又简称位指令,每一条指令完成一个
独立的算术或逻辑运算操作。如:操作数的加、减、乘、除、传递、移位、比较等。宏指令
由若干条机器指令构成。

1.S/390 指令格式
一条指令通常是由操作码和地址码两大部分组成:
操作码 地址码
OP 操作数

指令的左半部分是操作码,它指出所要执行的操作,既表明指令的操作特性和功能,
S/390 中采用 8 位定长操作码(S 型指令扩展到 16 位);右半部分是地址码,通常指参加运
算的操作数。操作数可以放在内存、寄存器或直接跟在操作码后面(立即数),此即是上一
节谈到的三种寻址方式。S/390 计算机系统的指令属于多地址指令,地址格式由一地址、两
地址和三地址。每个地址存放的数据类型由操作码决定。数据的长度用两种方法确定,一是

- 12 -
由操作码确定,而是在指令中用专门的字段给出数据长度。
2.S/390 指令类型
S/390 系列计算机系统中,大约有十五大类的指令,其中常用的由六大类。为了方便介
绍先把在描述指令类型中用到的符号作一简单说明:
OP:操作码,告诉 CPU 做什么操作
D: 位移量
B: 基地址寄存器
X: 变址寄存器
R: 通用寄存器
I: 立即数(1 各字节长)
L: 数据长度(参加操作的数据长度)
M: 屏蔽码
字母后面的数字代表操作数的序号。
⑴.RR 型(寄存器-寄存器型),2 字节指令,格式为:
OP R1 R2
8位 4位 4位
⑵.RX 型(寄存器-变址存储器型),4 字节指令,格式为:
OP R1 X2 B2 D2
8位 4位 4位 4位 12 位
⑶.RS 型(寄存器-存储器型或称三地址指令),4 字节指令,格式为:
OP R1 R3 B2 D2
8位 4位 4位 4位 12 位
⑷.SS 型(存储器-存储器型),6 字节指令,格式为:
OP L B1 D1 B2 D2
8位 8 位 4 位 12 位 4 位 12 位
或者
OP L1 L2 B1 D1 B2 D2
8位 4 位 4 位 4 位 12 位 4 位 12 位
⑸.SI 型(存储器-立即数型),4 字节指令,格式为:
OP I2 B1 D1
8位 8 位 4 位 12 位
⑹.S 型(存储器型),4 字节指令,格式为:
OP B2 D2
16 位 4 位 12 位
这类指令比较特殊,为系统控制指令,绝大多数为特权指令。观察指令格式可以注
意到此类指令有一个隐含的操作数,此操作数由系统自动提供。

第四节 S/390 汇编语言的数据表示

数据是程序处理的基本对象,本节讨论 S/390 汇编语言处理的数据类型及其表示形式。


S/390 系列计算机能处理以下四种数据类型:

- 13 -
1.字符数据
IBM S/390 计算机系统中采用 EBCDIC(Extended Binary Coded Decimal Interchange
Code)代码来表示字符型数据,这是一种比小型微型计算机系统中广泛使用的 ASCII 编码
跟早的字符编码体系。这种代码体系采用 8 个二进制位即一个字节来代表一个字符型数据,
因此最多可以表示 256 个字符。本书的附录中给出了 EBCDIC 代码表。
EBCDIC 编码中,字符数据分两部分组成,即前缀部分和数值部分。例如字符 A 的
EBCDIC 代码为 11000001,其书写格式为 C1,C(1100)为大写字母前缀;数字字符 1 的
EBCDIC 代码为 11110001,其书写格式为 F1,F(1111)位数字前缀。

2.二进制数
IBM S/390 计算机系统中的二进制数分为定点数和浮点数两种,用以表达整数和实数。
⑴.定点数
定点数根据其长度分为三种格式,即半字长(2 字节)的短定点数、全字长(4 字
节)的长定点数和双字长(8 字节)的超长定点数,它们所表示的整数的范围如下:

种类 表示范围
短定点数 –215 ~ +215–1
长定点数 –231 ~ +231–1
超长定点数 –263 ~ +263–1

定点数是将每位都作为二进制数据处理的,其最高位是符号位,用 0 表示正数,用
1 表示负数,负数用补码表示。处理定点数的指令是二进制指令。
⑵.浮点数
浮点数根据其长度分为三种格式,即全字长(4 字节)的短浮点数、双字长(8 字
节)的长浮点数和双双字长(16 字节)的超长浮点数。浮点数由三部分组成,即:符号位、
阶码和尾数,它们的构造如下所示,其中 S 代表符号位,C 代表浮点数的阶码部分,SW 代
表浮点数的尾数部分:
短浮点数: S C SW
1 位 7 位 24 位
长浮点数: S C SW
1 位 7 位 56 位
超长浮点数: S C SW
1 位 7 位 120 位
它们所表示的实数的范围如下(这里给出的是规格化数的绝对值范围):

种类 表示范围
短浮点数 16 ~ (1-16-6)*1663
-65

长浮点数 16-65 ~
(1-16-14)*1663
超长浮点数 16-65 ~
(1-16-28)*1663

3.逻辑数
逻辑数据是无符号的二进制数或字符串数据。逻辑数由定长和可变长两种表示形式。
⑴.定长逻辑数
定长逻辑数,长度为 32 个二进制位,表达数据范围为 0 ~ 232–1

- 14 -
⑵.可变长逻辑数
可变长逻辑数,为 1 个字节到 256 个字节长的二进制码或字符串。主要用于符号处
理中的字符数据。每个字符位 1 个字节长,用 EBCDIC 编码表示。
4.十进制数
十进制数的引入,是为了方便程序员识别计算机处理的数据。但是计算机最终只能处理
二进制数,所以十进制数据都必须借助二进制的形式来加以表达,在代码设计上既要考虑到
计算机要能方便地处理,又靠考虑到程序人员要能方便地阅读。这种类型的数据是可变长的
数据,特别适合于商业事务处理,今天仍在广泛的使用。
十进制数由压缩十进制数和非压缩十进制数两种表示形式。
⑴.非压缩十进制数
前面介绍字符数据的时候说过数字字符 1 的 EBCDIC 代码为 11110001,其书写格
式为 F1,F(1111)位数字前缀,数字字符 2 的 EBCDIC 代码为 11110002,其书写格式为
F2,F 位数字前缀,以此类推。非压缩的十进制就是把带前缀(即‘F’)的 0~9 的字符数字
按照十进制中的顺序排列在一起,并把最后一个字节的前缀换成符号位,‘F’和‘C’代表
正数,‘D’代表负数。其格式如下,其中 F 为前缀,数值 D 的取值范围为 0~9,S 为符号
标志:

F D F D F D S D

例如+1123 的非压缩十进制编码为 F1F1F2F3 或 F1F1F2C3;-1123 的非压缩十进制


编码为 F1F1F2D3。
⑵.压缩十进制数
所谓压缩的十进制数,就是一个字节中包含 2 个数值数字,即把非压缩十进制数据
中的前缀部分去掉,但符号标志仍然保留。其格式如下,其中数值 D 的取值范围为 0~9,S
为符号标志(含义与非压缩十进制相同):

D D D D D D D S

例如+1123 的非压缩十进制编码为 1123C;-1123 的非压缩十进制编码为 1123D。


由非压缩十进制变换到压缩十进制的过程示意图如下:

F D F D F D S D

D D D D S

图 2-6 十进制压缩示意图

第五节 S/390 汇编语言的基本成分

- 15 -
1.基本字符集
任何自然语言、高级程序设计语言都有它所允许的一定数量的字符,用以表述它的语言
成分。同样,S/390 汇编语言也有自己的字符集。它是根据外部设备的配置情况和汇编语言
本身的需要而规定的,汇编语言的基本字符集是 EBCDIC 编码字符集的子集。这些字符作
为汇编语言的基本元素可以构成各种语言成分。S/390 汇编语言的基本字符集有以下几类:
字母(大写) A,B,C,…,X,Y,Z
数字 0,1,2,3,…,7,8,9
其它符号 * + - / = , ‘ $ 空格 # @ & ( ) : . 等

2.符号名
我们知道,任何语言的基本语法成分是单词,它一般是由字符集中的字母和其它符号构
成。同样,汇编语言的基本语法成分之一是所谓的符号名,也称名字。名字是以字母开头的
字母数字串。名字的第一个符号必须是字母。在这里字母还包括$、#和@三个符号。组名字
的字符个数不得超过 8 个。名字分为两种,一种是系统定义的,例如机器指令的记忆码,汇
编控制语句的记忆码,系统宏指令的名字等。另一种是自定义的,即由程序员在程序中自行
定义的,如数据的名称,它是该数据在内存中的符号地址;又如语句的标号,它代表了语句
在内存中的开始位置。在一个名字中,禁止出现空格符或其它符号。例如,下面的名字是合
法的:
ADDRESS STD12 #357 X1 A
L38B @XXY $987 Y1 B2
下面的名字则是不合法的:
4RT (不是以字母开头)
X+Y (出现了非字母或数字或允许的特殊符号)
IN AREA (出现了空格符)
ABCDEFGHIJK (超过了 8 个字符)
β1 (非法字符)
对于汇编语言程序而言,名字一般要在名字字段(在本节第 5 点中介绍)出现。一旦名
字在名字字段出现过,则称它是有定义的。程序中使用的每个名字都必须(在某个位置)被
定义,而且在一个程序中,每个名字只能被定义一次。

3.项及表达式
为了便于描述符号地址,S/390 汇编语言允许用表达式表示地址。下面讨论表达式的构
成。
⑴.自定义项(常数)
其值由自身就能确定的项称为自定义项,也称为常数。其二进制值的长度不超过 3
个字节。自定义项有以下几种类型:
①.十进制自定义项。即通常的无符号十进制整数,例如,23,654,0,1024 等。
项内即不能由逗号也不能出现小数点。汇编程序把十进制数自动转换为二进制数。
②.十六进制自定义项。其书写形式为:X’十六进制数字串’,其中 X 为十六进制
自定义项的标志,单引号不得缺省。例如,X’89’, X’92AE4’, X’FFFF’等。
③.二进制自定义项。其书写形式为:B’二进制数字串’,其中 B 为二进制自定义
项的标志。每 8 个二进制占一个字节。例如,B’00110101’。
④.字符自定义项。其书写形式为:C’字符串’,这里 C 是字符自定义项的标志,
单引号中的字符是指除引号及&以外的所有 EBCDIC 编码字符。若在这种项中的必须出现引
号或&,则要将其连写两次。例如,C’B’’8’定义了三个字符:B’8。C’A&&B’定义了三个字

- 16 -
符:A&B。
在汇编语言中,以上四种定义项由被统称为常数,它们可以用来指明地址、寄存器
的编号等等。
⑵.符号常数
符号常数是一种用字符形式表示其数值的常数,即从字面上看便可以知道其“值”
的常数。其书写形式为:=常数。等号“=”是符号常数的标志,常数的书写形式如前所述。
例如=X’1F’,=B’1010’都是符号常数,其值分别是十六进制数 1F 和二进制数 1010。
符号长数值允许出现在指令语句(见本节第 4 点)的源操作数位置上,即在指令语
句中,允许直接书写常数作为操作数,这样可以使得程序设计得以简化。
⑶.项及表达式
。用+、-、*、/等运算符联系起来的项的组合形式就称为表达式。例如 9+8,
5*TP,X’DC8’-16 等都是表达式。表达式内可以根据代数规则使用圆括号。为了能正确地书
写表达式,应遵守下面的规则:
①.表达式不允许以运算符开头。例如-7+K 是不允许的,但改写成 0-7+K 或 K-7
是允许的。
② . 表 达 式 内 的 两 个 运 算 符 不 得 并 置 , 两 各 项 也 不 得 并 列 。 例 如 B1*-5 ,
X’EF’B’1011’+14 是不允许的,前式中出现两个运算符并置的情况,后式中出
现两个项的并列。
③.若表达式中有*号,则*号不得省略或用.代替。例如 B*(A-2)不得写成 B(A-2)
或 B.(A-2)。
④.禁止在含有两项或两项以上的表达式中使用符号常数,例如=X’14F008’+14。
⑤.除法仅取商作为结果,略去余数,并且不进行舍入。除数位 0 不认为是错误,
此时结果为 0。
⑥.若表达式的第一项为*号,则此*号不再是乘号含义,而是指出现该表达式所在
的那条指令最左边一个字节的地址,即指令计数器的当前值。在这种情况下,
作为乘号用的*号不得同上述意义的*号混为一谈。例如,*+8 ,*+A-P,*-12
等表达式中的*号均指指令计数器的现行内容。而表达式 7*(Q-P)-12 中的*号
则表示乘法运算符。
⑷.绝对表达式和相对表达式
如果表达式的值不因程序在内存中的移动而改变,则称这种表达式为绝对表达式,
否则称为相对表达式。例如,若 BL1 是以指令的名字,则表达式 BL1+16 就是一个相对表
达式,而表达式 1024 是绝对表达式。若 BL2 是相应程序中另一指令的名字,则 BL1- BL2+16
就是一个绝对表达式了。这是因为两条指令之间的距离总是一常数,它不会因为程序在内存
中的移动而改变。
在汇编语言程序中,表达式一般仅用于表示(或计算)地址,因此也有人称之为地
址表达式。一般说来,地址都是比较简单的,它们或为单个项或为仅含+、-运算符的表达式。

4.汇编语句的分类和一般格式
S/390 计算机汇编语言由四种类型的语句,它们是:
⑴.注释语句
注释语句是为了便于阅读程序而提供的一种附注型语句。它对程序的执行不产生任
何影响。注释语句可出现在源程序的任何语句之后,但至少要有一个空格符与该语句相隔;
也可单独作为一行或多行出现,单每行的第一个符号须用*号标明。
⑵.指令语句

- 17 -
机器指令的符号形式。此类语句都在目标程序中生成一条相应的机器指令。
⑶.汇编控制语句
它是汇编程序在翻译汇编语言程序的过程中提供某些信息或某种控制命令的一类语
句。汇编控制语句一般又称为伪指令,一般不会在目标程序中出现。
⑷.宏指令语句
为了简化程序设计,把一些由一组机器指令组成的常用程序段(如 I/O 程序)定义
成宏指令,然后在程序中调用即可。宏指令可分为系统宏指令和用户定义的宏指令。宏指令
的定义方法我们将在后续的章节中讨论。

5.机器指令的符号形式
在本章第三节中我们已经介绍了六种常用的指令格式。对于每一种指令格式,都有相应
的符号表示形式,即指令语句形式:
⑴.RR 型
[名字] 记忆码 R1,R2
方括号[ ]表示其中的项可以省略。R1、R2 为绝对表达式,它们的值为 0~15 之间的
无符号整数,用以指明寄存器的编号。但在这类指令中,有两条指令例外,它们的符号形式
为:
[名字] SPM R1
[名字] SVC I
其中指令 SPM(建立程序屏蔽指令)不含有 R2 部分。指令 SVC(访管指令)中
的 I 唯一绝对表达式,它取从 0~255 内的无符号整数,表示操作数为直接量。
⑵.RX 型
[名字] 记忆码 R1,D2(X2,B2)
或:[名字] 记忆码 R1,S2(X2)
第一种形式成为显式地址,第二种地址形式称为隐式地址。前种形式中的 D2(X2,
B2)用以确定第二操作数,X2、B2、D2 都是绝对表达式。当 D2=0 时,不的略写为(X2,
B2),必须写成 0(X2,B2)。若 X2=0,则应写为 D2(0,B2)或 D2(,B2)。如果 X2 和
B2 都等于 0,则此括号中的项可省写,在这种情况下,第一种形式为:[名字] 记忆码 R1,
D2。S2 为第二操作数的地址表达式(下同)。当 X2=0 时,第二种形式为:[名字] 记忆码 R1,
S2。
⑶.RS 型
[名字] 记忆码 R1,R3,D2(B2)
或:[名字] 记忆码 R1,R3,S2
由于 RS 型指令中的所有移位指令都不含有 R3,所以它们的符号形式为:
[名字] 记忆码 R1,D2(B2)
或:[名字] 记忆码 R1,S2
⑷.SI 型
[名字] 记忆码 D1(B1),I2
或:[名字] 记忆码 S1,I2
必须注意,在汇编语言的指令语句中,其所含的操作数是按逻辑次序书写的,并非
按它们在机器指令中出现的次序书写,请读者自行比较两者的区别。
⑸.SS 型
[名字] 记忆码 D1(L1,B1),D2(L2,B2)
或:[名字] 记忆码 D1(L1,B1),S2(L2)

- 18 -
或:[名字] 记忆码 S1(L1),D2(L2,B2)
或:[名字] 记忆码 S1(L1),S2(L2)
其中,S1 为第一操作数的地址表达式,如果操作数的长度可以确定,则长度 L1 可
省略,即可缩写为:Di(,Bi)或者 Si(i=1,2)。按此种方式给定操作数长度的方法称为
隐含方式。机器指令中的长度是 0~15 之间的一个无符号整数,比实际长度少 1。在符号形
式中,作为长度的表示式要指明实际长度,也就是说,要给出实际的字节数。
对于只含一个长度的 SS 型指令,其符号形式为:
[名字] 记忆码 D1(L,B1),D2(B2)
或:[名字] 记忆码 S1(L),S2
或:[名字] 记忆码 D1(L,B1),S2
或:[名字] 记忆码 S1(L),D2(B2)
或:[名字] 记忆码 S1,S2
或:[名字] 记忆码 D1(B1),S2
或:[名字] 记忆码 S1,D2(B2)
在这种情况下,认为第一操作数具有隐含长度,也就是说,当第一操作数字段为表
达式时,该表达式第一项定义的存储区域的长度就是隐含的长度。例如,若 XY7 是长度为
8 个字节的一个存储区域的名字,而 XY5 是长度为 4 个字节的另一个存储区域的名字,它
们的隐含长度分别是 8 和 4。
⑹.S 型
[名字] 记忆码 D2(B2)
或:[名字] 记忆码 S2
这种指令仅有第二操作数。
除了这里介绍的六种指令的符号形式以外,汇编语言为条件转移指令还提供了一组扩充
记忆码。扩充记忆码不仅规定了机器转移指令,而且还指明了要转移的条件,这使程序设计
者不必写出指令中的屏蔽码(后续章节中有介绍),就可获得所需的条件转移,给使用条件
转移指令提供了方便。各种扩充记忆码机器等价的机器指令详见转移指令扩充的助记忆码
表。

6.汇编语言程序的书写格式
早期计算机系统的输入设备一般采用读卡机,程序员把写好的程序用穿孔设备在空白的
卡片上打孔,为了让读卡机能够正确地读出程序和数据,程序和数据在卡上的格式必须加以
严格的规定。虽然后来人们不再使用卡片和读卡机,但是程序的书写格式却保留了下来,以
下是 S/390 汇编语言的书写格式要求:

1 8 9 10 14 15 16 71 72
名字域 操作码域 操作数和注释
PROG1 CSECT
BALR 9,0 THIS IS ONLY AN EXAMPLE
USING PROG1,15
……

图 2-7 汇编语言的书写格式

其中 1~8 列为名字字段,用于书写语句名或标号,10~14 用于书写操作码,16~71 为操


作数和注释区,用于书写操作数和必要的注释语句,72 列为续行列,当一条语句一行写不

- 19 -
下去时,在 72 列写入一个非空格字符。语句的继续部分必须在下一行的第 16 列开始。第 9
和第 15 列必须是空白。

习题二

1.“冯·诺依曼”计算机结构的工作原理是什么?
2.程序状态字 PSW 的作用是什么?
3.S/390 有哪些编址单位?它们所对应的整边界有什么特征?
4.主存储器、扩充存储器、辅助存储器中一个长度为 4K 字节的连续空间叫做什么?它们用
来做什么?
5.通道子系统在 S/390 系统中的作用是什么?
6.S/390 有哪些寻址方式?各有什么特征?
7.逻辑地址到物理地址的转换有哪几种方法?各有什么特征?
8.S/390 有哪几种常用的指令类型?
9.S/390 有哪几种数据类型?
10.请写出以下数据的非压缩十进制和压缩十进制形式:
38 -76 1234 -9876
11.请指出下列符号名哪些是合法的,哪些是非法的,若是非法的,请说明理由。
ASSEMBLY 5WY FKK12 #22 AA-BB WY1 Z
N007 BEI JING @PHIL $2000 THISISANEXAMPLE ΦK
12.汇编语句有哪几种?各有什么作用?

- 20 -
第三章 汇编程序和汇编控制语句

第一节 汇编程序 Assembler

前面我们已经介绍过,同机器语言相比,汇编语言可以使用符号记忆码、符号名字和符
号地址进行程序设计,因此,书写和阅读都比较方便、直观,也便于记忆、查找和维护。然
而,应指出的是,机器语言才是机器能理解的语言,机器不能直接执行符号式的汇编语言程
序,而必须先将其翻译加工成为机器语言程序,然后机器才能执行。这种翻译工作是由汇编
程序(Assembler)完成的。汇编程序是一种系统程序,它将汇编语言程序(即源程序)翻
译成相应的目标程序。这一翻译过程称为汇编过程或汇编。
汇编程序的主要工作是进行存储分配和翻译。具体地说,它就是分别对程序中的四种不
同类型的语句进行相应的处理:
对于指令语句,它把每一指令翻译成对应的一条机器指令;
对于宏指令语句,它根据宏定义,把宏调用语句扩展成相应的语句序列,并将此语句序
列置于宏调用的位置上,直至最后与其它语句一起翻译成目标程序;
对于汇编控制语句,汇编程序将在汇编过程中按语句的要求采取相应的动作,以完成语
句的功能。例如,规定目标指令存放在内存的起始点,留出多大的工作区和输入输出区,将
那些常数送入目标程序,哪些寄存器可以作为基址寄存器使用等等;
对于注释语句,汇编程序只在汇编清单中原样输出,供程序设计者查阅和存档,它们不
被翻译到目标程序中。
汇编程序加工汇编语言程序的过程通常分成两个阶段。第一阶段(也称第一趟扫描)主
要是按源程序的要求进行存储分配。即扫描源程序,加工其语句,确定名字、常数及指令在
程序中的相对位置,并建立一个符号表(或名字表),将名字的有关信息登记在表中;对于
符号常数,还要建立一个符号常数表,登记其有关信息。第二阶段(也称第二趟扫描)主要
是对指令语句进行翻译,生成目标模块。即第二次扫描源程序并根据第一阶段建立的表,加
工每一语句,生成正确的机器指令(这包括确定基地值寄存器,计算位移量)或相应的常数,
以及打印汇编清单等。
早期,用于将汇编语言程序转换为可执行代码的过程如图3-1所示。

源程序

汇编程序

汇编清单 内存中的可执
行程序

图3-1 汇编程序工作示意图一

- 21 -
源文件(即汇编语言程序)由汇编程序编译为程序的可执行代码。如在汇编期间在源程序
上未发现错误,程序的转换代码的执行可能会立即开始。这当然是执行程序最直接的方法,
对于大部分打算执行一次或最多几次的短程序是令人满意的。然而,在通常情况下,典型的
程序趋于执行许多许多次。在这样的情况下,应当避免每次在执行一个程序时都要使用汇编
程序翻译源文件。因此,现在基本上采用类似于图3-2的过程:

源程序

汇编程序

汇编清单 目标模块
Object Module

装载程序

内存中的可执
行程序

图3-2 汇编程序工作示意图二

在这一过程中,汇编输出的程序可执行版本以目标模块形式保持,装载程序(Loader)
就是从目标模块中拷贝翻译后的代码至存贮器中以便程序可以开始执行。
因此,一旦获得了一个完全调试好的源程序,只需要在程序第一次执行时利用图3-2的
全过程。在所有其它的情形下,程序的执行只须简单地利用括在间断线中的部分过程来完成。
即一旦目标模块被创建,程序可以被随时准备好以便执行:仅利用装载程序从目标模块中将
翻译好的代码拷贝至存贮器中。

第二节 汇编控制语句

在第二章以及前一节中我们已经对汇编控制语句做了简单的介绍。虽然汇编控制语句在
目标程序中不产生任何机器指令,但是它们告诉汇编程序如何进行汇编。程序中若没有汇编
控制语句,汇编程序将无法把汇编语言源程序翻译称为目标程序,所以汇编控制语句在汇编

- 22 -
语言程序设计中是一个非常重要组成部分。汇编控制指令通常由被称为伪指令。本节将对它
们作详细的介绍。
1.数据与数据区域的定义
一个完整的汇编程序是由机器指令和数据组成,汇编程序必须定义存放数据的位置、数
据区域的大小、属性等等。这些都是由定义数据和数据区域的汇编控制指令完成。
⑴.数据区域的定义
数据区域的功能是定义程序中使用的数据区域,通常程序需要内存区域存放以下几
种数据:
①.程序从输入设备(终端、磁盘等)读入的数据;
②.中间结果;
③.准备输出的数据;
④.常数。
数据区域的定义用 DS(Define Space)语句,DS 语句不会生成任何数据,只是保
留内存。DS 语句的基本格式为:
[名字] DS DTLn
名字:数据区域的名字,同时也代表该存储区域第一字节的地址。
D: 复写因子,相同数据的重复个数,省略为 1。若复写因子为 0,则不保留实
际区域,而是用来对齐地址边界和获得某个存储区域的名字。
T: 数据区的类型:
C = 字符
H = 半字
F = 全字
D = 双字
X = 十六进制数
B = 二进制数
P = 压缩十进制数
Z = 非压缩十进制数
Ln: 用十进制给出的区域的长度(单位为字节),缺省值由类型决定。
例 1:
AREA DS CL4
AREA2 DS 4CL1
第一个定义保留 4 个字节的区域,并将该区域的首地址赋予 AREA,该区域
的长度为 4 字节;第二个定义同样保留了 4 个字节的区域,并将该区域的首地址赋予 AREA2,
但该区域的长度为 1 字节。
例 2:
FIELD1 DS XL256 保留 256 个字节
FIELD2 DS X’457’ 保留 2 字节,而不是赋值 457
CODE DS C 保留 1 字节
PARTND DS CL8 保留 8 字节
TABLE DS 100CL5 保留 500 字节,长度为 5 字节
K1 DS C’A’ 保留 1 字节
K2 DS CL’A2’ 保留 2 字节
Z1 DS Z’54321’ 保留 5 字节
DBLFLD DS D 保留 8 字节,双字对齐

- 23 -
例 3:定义一个存放具有多个字段的纪录的存储区域:

名字 操作码 操作数 相对地址


INAREA DS 0CL80 1000
NUMBER DS CL5 1000
NAME DS CL21 1005
WORK DS CL8 101A
ADDR DS CL20 1022
CITY DS CL15 1036
STATE DS CL11 1045
在上面这个例子中,我们定义了一个 80 个字节的名位 INAREA 的区域,有将其分
成几个带有自己名字的子区域。注意这里的 INAREA 使用了 0 复写因子,它只给出了数据
区域的长度为 80 字节,名字为 INAREA 并没有分配空间。真正的空间分配是后续的语句完
成的。
⑵.常数的定义
DS 只是定义了数据区域的空间和位置,不包含数据。一般程序中通常要包含一些
常数,它们通常作为程序的一部分放在内存。在汇编语言中,用 DC 语句定义常数。DC 语
句的基本格式为:
[名字] DC DTLn’C’
名字:数据的名字,同时也代表该数据第一字节的地址。
D: 复写因子,相同数据的重复个数,省略为 1。若复写因子为 0,则不保留实
际区域,而是用来对齐地址边界和获得某个存储区域的名字。
T: 数据的类型:
C = 字符
H = 半字
F = 全字
D = 双字
X = 十六进制数
B = 二进制数
P = 压缩十进制数
Z = 非压缩十进制数
Ln: 用十进制给出的区域的长度(单位为字节),缺省值由汇编程序计算其长度。
C: 常数自身,字符型常数必须用“’”号括起。
①.字符常数的定义
一般格式为:
[名字] DC DCLn’字符’
例 1: AREA1 DC CL5’1234’
F1 F2 F3 F4 40
不足处用 EBCDIC 代码空格 40 填补。
例 2: AREA2 DC CL’123456’
F1 F2 F3 F4 F5 F6
②.二进制常数的定义
一般格式为:
[名字] DC DBLn’二进制数’
给出长度值 Ln 时,若它与常数值不一致,则进行左边截断或填补 0 处理。
例 1: BIN1 DC BL2’110100111010’

- 24 -
2 字节
0000 1101 0011 1010
补4位0
例 2: BIN2 DC BL1’110100111010’
1 字节
0011 1010
定义了 1 字节长(8 位)的二进制数,常数值有 12 位,截断左边
的 4 位。
例 3: BIN3 DC B’110100111010’
2 字节
0000 1101 0011 1010
补 4 位 0(隐含长度取整,即系统计算时,以字节为单位)
③.十六进制常数的定义
一般格式为:
[名字] DC DXLn’十六进制数’
给出长度值 Ln 时,若它与常数值不一致,则进行左边截断或填补 0 处理。若
十六进制数的个数为奇数,则左边填补 4 位 0。
例 1: HEX1 DC X’FFF’
2 字节
0F FF
补0
例 2: HEX2 DC XL3’F23C0’
3 字节
0F 23 C0
奇数补 0
例 3: HEX3 DC XL3’F23C0AB’
3 字节
23 C0 AB
截去 F
④.压缩十进制常数的定义
一般格式为:
[名字] DC DPLn’十进制数’
每个十进制数占半个字节,不论有无符号都在最后产生符号位,C 表示正,D
表示负。给出长度值 Ln 时,若它与常数值不一致,则进行左边截断或填补 0
处理。
例 1: CON1 DC P’123’
2 字节
12 3C
符号
例 2: CON2 DC P’1234’
3 字节
01 23 4C
补0 符号
例 3: CON3 DC PL2’-73424’
2 字节
42 4D

- 25 -
丢失 73 符号
P 型允许多个常数,若多于一个常数,可用逗号分开。
例 4: CON4 DC PL3’46,-4,+7234’
9 字节
00 04 6C 00 00 4D 07 23 4C
例 5: CON5 DC P’ 46,-4,+7234’
6 字节
04 6C 4D 07 23 4C
本例说明如果用户未给出数据长度,由系统计算长度。P 型数的最大长
度是 16 个字节。
⑤.非压缩十进制常数的定义
一般格式为:
[名字] DC DZLn’十进制数’
每位十进制数占一个字节,每位数的前缀为 F,最后一位的前缀为符号位,C
表示正,D 表示负。给出长度值 Ln 时,若它与常数值不一致,则进行左边截
断或填补 0 处理。
例 1: ZEX1 DC Z’123’
3 字节
F1 F2 C3
符号位
例 2: ZEX2 DC Z’46,-4,+7443’
7 字节
F4 C6 D4 F7 F4 F4 C3
⑥.定点常数的定义(H 型或 F 型)
H 型是半字长的定点整数(2 字节),F 型是全字长的定点整数(4 字节)。
一般格式为:
[名字] DC DH’十进制数’
[名字] DC DF’十进制数’
汇编程序自动将十进制数转化为二进制数(负数用补码表示) 。
例 1: HALF1 DC DH’80’
00 50
FULL1 DC DF’80’
00 00 00 50
例 2: HALF2 DC DH’-3’
FF FD
FULL2 DC DF’-3’
FF FF FF FD
⑦.地址常数的定义
地址常数是用作地址的常数,它用 DC 语句建立,汇编程序将语句中出现的地
址表达式译成常数,地址常数有 A,Y,S,V 和 Q 五种类型。下面分别介绍之:
* A 型地址常数
汇编语言把 A 型地址常数规定成绝对的或浮动的表达式,其书写格式为:
ALn(表达式)
汇编程序计算表达式的值,并将其作为二进制整数存放起来。长度码 Ln,
用以指明该常数存储区的大小。当 Ln 缺省时,A 型地址常数的隐含长度是 4 字节。
在规定了长度值时,不进行边界对齐,例如语句:

- 26 -
DC A(*+30)
它规定了这样一个地址,其指令计数器的当前值加上 30 后,以二进制形式
存储在全字中,这个全字在整数边界上对齐。例如语句:
ADCON1 DC A(START+4096)
ADCON2 DC A(START)
前一语句建立全字长的地址常数,该常数具有等价的十六进制地址,它等于
4096 加上符号名 START 之值;后一语句将建立一个具有符号名 START 之值的地
址常数,也是一个字长。在这里,每个 A 型地址常数都要字边界上对齐。
A 型地址常数除了用 DC 语句建立之外,还能以符号常数的形式来建立,例
如:
=A(START+400)
=A(*+4)
对于 A 型地址常数,在 DC 语句中一次可以建立多个,例如:
ACON DC A(BASE+4096,BASE+8192,BASE)。
* Y 型地址常数
Y 型地址常数与 A 型地址常数很相似,不同之处在于它隐含了 2 个字长的
长度,并在半字边界上对齐。如果对 Y 型地址常数指定了长度值,则不进行边界
对齐。例如语句:
YDCON1 DC Y(START)
YDCON2 DC Y(*+4096)
都建立半字常数,且在半字边界上对齐。Y 型地址常数除了用 DC 语句建立之外,
还能以符号常数的形式来建立,例如:=Y(START+400),=Y(*+4)等。
* S 型地址常数
该类型的常数按其地址和位移的形式来存储地址。若为规定长度码,则常数
按半字建立,并在半字边界对齐。可以用两种不同的方法来规定这种常数:
[名字] DC S(表达式)
此时汇编程序将地址表达式(绝对或浮动的均可)之值分解成地址寄存器及
位移量。例如语句:
SDCON1 DC S(ALPHA)
将 ALPHA 之值分解成基地址寄存器及位移量的形式存储在半字单元 SDCON1
中。
SDCON2 DC S(600(9))
将地址常数 9600(用十六进制形式书写)存储在单元 SDCON2 中,请注意,被
汇编的常数的最左边四位二进制代表基地址寄存器,余下的 12 为二进制表示位移
量,不能把 S 型地址常数书写成符号常量。
* V 型地址常数
它类似于 A 型地址常数,差别仅在于表达式必须是浮动的,这种常数用来
保留外部符号地址存储单元。这种外部符号是在其它程序段中定义的,V 型地址
常数用来建立和其它程序段的连接。它所规定的常数不需要用 EXTRN 语句来标
识。这类常数隐含的长度是 4 字节,并按全字节边界对齐。例如:
VCON1 DC V(RTN)
注意:对于外部数据(在其它程序段中定义的数据)的访问不能直接使用 V
型常数。
* Q 型地址常数

- 27 -
该类型的地址常数不常使用,此略之。

2.程序的构成控制
在编写一个大程序时,通常把它分解成几个小程序来编写,每个小程序完成一个功能,
它们单独汇编、单独调试,最后用连接编辑程序(装载程序)将其组成一个大程序而执行。
这样分割后程序变小了,功能单一了,编写和调试都比较容易。另外在对已编好的程序进行
修改和扩充,总是针对某个功能而言,所以只要对与某个功能有关的模块进行修改或编写新
的模块,其它模块可以不变。把分割后的小程序称为一个模块,经过汇编后的模块称为目标
模块。一个或多个目标模块经连接编辑后形成可执行程序。这种程序设计方法称为模块化程
序设计。采用模块化程序设计方法设计的程序,具有就够比较清晰、紧凑、容易读、易于交
流、便于修改和扩充等优点。
另外,对于不太大的程序,不用模块化程序设计方法,而是分成几个程序段(Program
Section),段有时也被称为节,一段完成一个功能,整个程序一起汇编,生成一个目标模块,
但目标模块是由几个独立的控制段组成的,这样的模块只有经过连接编辑程序加工成装载模
块(Load Module)才可以执行。这样的程序是一段一段的,所以也能达到结构清晰、易读
易维护的要求。在 S/390 计算机系统中,控制段是在存储器中的再分配的最小单位,也是连
接编辑程序加工的最小单位。程序段由两种:控制段(Control Section)和虚拟段(Dummy
Section)。它们分别由控制语句 CSECT 和 DSECT 定义,另外程序中的第一个控制段通常用
控制语句 START 定义。图 3-3 是一个包含多个程序段的程序示意图:

SD START 控制段 SD
……
BET CSECT 控制段 BET
……
GAM CSECT 控制段 GAM
……
DF DSECT 虚拟段 DF
……
END

图 3-3 程序段示意图

⑴.开始语句 START
START 语句是用于定义程序开始的语句,也表示某控制的开始语句,其格式为:
名字 START 自定义项
其规则是:
①.名字域的符号将作为程序段的名,长度属性为 1,如果没有名,则作为无命名
的程序段;
②.操作数自定义项给地址计数器送初值,操作数省略时,表示给地址计数器送的
值为 0;
③.自定义项的值应使 8 的倍数,若不是 8 的倍数,汇编需要重新给出比现行值稍
大,而且离它最近的一个为 8 的倍数的值。如果是 START 1802 语句,则汇
编看成是 START 1808;
④.START 确定的地址是可以改变的,因为连接编辑程序必要时可以移动任何程
序;
⑤.START 指令语句省略的情况下则认为:

- 28 -
空格 START 空格
⑥.在每个独立翻译的程序部分不得有多于一个 START 语句,而且在这个语句前
也不应有使用地址计数器内容的语句。
⑵.结束语句 END
END 语句用以通知汇编程序,程序被汇编完毕。所以所有用汇编语言编写的程序
中,最后一个语句必须是 END 语句,其形式为:
END 表达式
其规则是:
①.操作数表达式必须是可移动的表达式,用以表示目标程序中最先执行的地址,
即起始地址;
②.操作数如果省略的情况下,就以目标程序的开始地址作为开始执行地址。
⑶.控制段语句 CSECT
CSECT 语句是指定控制段开始或表示前面同名控制段的继续。在这个 CSECT 指令
后面实现的全部语句组(直到出现不同的控制段指定语句或 END 语句为止)全部看作这个
控制段的一部分进行汇编,一个程序中可以包括多个 CSECT 语句。
CSECT 语句的格式如下:
名字 CSECT
其规则是:
①.名字域符号的值表示程序段开始的第一字节的地址。如省略时被认为程序段是
无命名程序段。但程序中只能由一个无命名的程序段;
②.一个程序段可以分散成几部分,但每一部分都应以不同名的 CSECT 为每部分
的开始,然而在汇编时则将分散在各处的语句都顺次地连接起来,作为一个完
整的程序段。在汇编时,每一程序段都应引入自己的计数器,换言之在每一程
序段的开始时计数器的初值为 0,对同名而由分散在各地的程序段部分汇编
时,这一部分结束式的计数器内容应该是下一部分的继续。例如:
形式地址 控制段 汇编后的顺序
000000 SCD CSECT
…… …… ① 000000 SCD ①
000800 DS CL4
000000 TRD CSECT SCD ③
…… …… ②
000100 DS CL4
000804 SCD CSECT 000904 TRD ②
…… …… ③
000900 DS CL4
000000 ABC CSECT 000A04 ABC ④
…… …… ④
000200 DS CL4
END
③.在程序的开始部分,如果没有使用有名的 CSECT 语句(或 START)语句,则
汇编时作为无名的程序段处理。
⑷.虚拟段语句 DSECT
DSECT 语句是定义虚拟段的语句,它表示虚拟段的开始或前面同名的虚拟段的继
续。在一个程序中可以出现多个虚拟段,它的格式如下:

- 29 -
名字 DSECT

其规则是:
①.名字域符号可以移动的,其值表示虚拟段的始址,长度属性为 1;
②.虚拟段的始址,地址计数器置为 0;
③.在一个程序里面,同名的 DSECT 指令被多次使用时,以最初出现的 DSECT
表示虚拟段的范围,直到下一个程序定义接续,地址计数器也以它接续虚拟段
的范围,直到下一个程序定义语句或 END 语句为止;
④.为了能在任何程序中使用虚拟段的格式,必须定义基本地址寄存器:
USING 虚拟段名,寄存器
即以虚拟段名作为基地址,由于虚拟段是从 0 开始的,所以形成一个形式地址。
在使用虚拟段中定义的记号名之前,必须把实际区的地址置入基地址寄存器
中,才能形成实际地址;
⑤.虚拟段中一般使用 DS 语句,对于 DC 语句和机器指令只根据指令的长度和
DC 语句所定义的常数的区域长度进行地址计数,不生成常数和指令。
⑸.公共段语句 COM
COM 语句是用来定义公共使用的存储区域,称为公共段,是引用段的一种。其形
式如下:
名字 COM
其规则是:
①.每个程序内均可确立多个公共段,在同一源程序中使用多个公共段时,凡是名
字域符号相同者,都将连成一个公共区;
②.如果在两个或两个以上的程序段内都定义了公共段,则形成公共段的大小,等
于各段程序内公共区最大的一个;
③.公共段的范围直到下一段控制语句和 END 为止;
④.名字域的符号为可移动的,其值为公共段最初一个字节的地址,长度属性为 1;
⑤.公共段中常数和确保常数指令只的区域和指令的长度区域,不生成具体常数和
指令;
⑥.名字域为空白时,定义为无命名的公共段。
下面是一个公共段使用方法的例子:

源程序 1 源程序 2
PROG1 CSECT PROG2 START
…… ……
L 10,AREA L 10,AREA2
USING BUFFER,10 ① USING BUFFER,10 ③
…… ……
ST 6,PTR L 5,PTR
…… ……
AREA DC A(BUFFER) AREA2 DC A(BUFFER)
…… ……
COM COM
BUFFE DS CL80 BUFFER DS CL80
R
PTR DS F ② PTR DS F ④
…… ……
POOL DS CL500 END

- 30 -
END

PROG1
(①部分)
PROG2
(③部分)
PROG1 的 COM 区
(②部分)

图 3-4 公共段合并示意图

图 3-4 是由两个控制段的源程序和目标程序结合起来执行程序的对应表示图。两个
程序的公共段被看成 1 个而结合起来,由于程序 PROG1 的公共段比程序 PROG2 的公共段
大,则公共段区域大小按给出最大容量的程序(PROG1)规定。在每个程序中引用公共段
时必须遵守下列方法:
①.定义 A 型地址常数指向公共区如程序 PROG1 中的
AREA DC A(BUFFER);
②.指定一个寄存器指向公共区,并用 USING 说明,如程序 PROG1 中的
L 10, AREA
USING BUFFER,10;
③.至此用程序 PROG1 中的 ST 5,PTR 那样的编码即可自由地引用公共段的内
容了。

3.地址的指定
计算机工作之时,指令和所要处理的信息数据都存放在内存中,在指令的地址码中,指
定了指令所要处理的信息或数据所在的地址。因此汇编程序对源程序汇编时要进行内存地址
分配。所以设立了累计指令长度地址计数器,地址计数器初值一般为 0,当 START 语句的
操作分量中指定时例外,地址计数器的值可以用 ORG,CNOP 等汇编控制指令强行改变,
也可被 USING、ORG、DC 等汇编控制指令用*号来引用。前面谈到,内存地址的表达方式
是:
基地址寄存器+变址寄存器+位移量
汇编语言的机器指令指定可以是显式方式和隐式方式,对于隐式方式的指定,如果没有
指定基地址寄存器及基地址,汇编程序就无法进行汇编,无法计算位移量。对汇编语言提供
这两个信息的是 USING 汇编控制指令,解除这两个信息的是 DROP 控制指令,下面详细地
介绍这几条指令。
⑴.USING 语句
USING 语句是使用基地址寄存器的语句。其功能是告诉汇编程序,使用哪一个寄
存器作为基地址寄存器,同时把目标程序执行期间这些寄存器将包含的地址值指示给汇编程
序。汇编控制语句 USING 语句格式为:
名字 USING 表达式,寄存器 1,寄存器 2,……,寄存器 n
其规则是:
①.表达式可以是绝对的,也可以是浮动的,其值为(0~4095)并以它作为基地址。
所指定的寄存器作为基地址寄存器,其赋以的基地址分别是:寄存器 1 为表达
式的值,寄存器 2 为表达式+4096 的值,……,寄存器 n 为表达式+(n-1)*4096
的值。即两个寄存器之间相差 4096;

- 31 -
②.0 号寄存器不能作为基地址寄存器;
③.如果在 USING 中确定 0 号寄存器作为操作数,则 0 必须写在寄存器 1 的位置
上,而表达式的值必须为 0,在这种情况下,汇编认为 0 是 0 号寄存器的值,
寄存器 2 的值为 4096;
④.对同样的寄存器被 USING 定义过多次的情况下,以最后一次定义有效;
⑤.USING 语句虽然给出定义基地址寄存器的值,但只是给汇编提供信息,并非
把值送到基地址寄存器中,真正给寄存器置入地址要到目标程序值形式由指令
来实现。基本方法如下:
BALR 2,0
USING *,2
……
BALR 2,0 是将下一条机器指令的地址送到 2 号寄存器,而在 USING *,
2 里,*表示现在地址计数器的值(亦称现行值) ,即告诉汇编将下一条指令地
址作为基地址并以此地址作为相对地址进行汇编,在程序执行时,执行 BALR
2,0 指令后,就把实际地址送入 2 号寄存器,于是下面的指令就能正确执行
了;
⑥.对于同一区域内,有 2 个以上的基地址寄存器有效时,汇编程序根据以下原则
来决定基址寄存器。把位移小的一方作为基地址寄存器。位移值相同时,把寄
存器编号最大的一方作为基地址寄存器;
⑦.每个程序节都必须定义基地址寄存器,否则这个节中的各名字域中定义的名字
无法作为符号地址使用。对于控制节来说:USING 语句必须在本控制节中定
义,一般放在控制节的前面。对于引用节来说,USING 语句可以在本节定义,
也可以在其它控制节中定义,但必须在使用引用节中的名字作为隐式地址的机
器指令之前。
⑵.DROP 语句
DROP 语句的功能是取消由 USING 语句规定的基地址寄存器,其格式为:
DROP 寄存器 1,寄存器 2,……,寄存器 n
如果操作数域为空,这时汇编认为,所有现行的基地址寄存器均被解除。
⑶.ORG 语句(置地址计数器)
ORG 语句为强行更改地址计数器的值,其格式为:
ORG [表达式]
其规则为:
①.地址计数器被变换为操作数域中表达式的值;
②.操作数域的表达式值必须为正,并且是可浮动的,也就是说表达式无论取何值,
都不应小于程序得初始地址,否则无法给定基地址寄存器的新值;
③.操作数域的表达式被省略时,则地址计数器所置的值为这个语句出现之前分给
程序的最大地址再加 1;
④.操作数域的表达式使用名字时,则此名必须在该语句之前的语句中定义。
例如: ORG CARD1
指令计数器的值置为符号名 CARD1 的值。
⑷.CNOP 语句(条件不操作)
CNOP 语句是将地址计数器的现行值强行变换为所要求的边界值,主要用来使 C、
X 型常数在汇编时要求分配在对应边界内时进行对齐边界。其形式为:
名字 CNOP b,w

- 32 -
其中,b,w=0,4;0,8;2,4;2,8;4,8;6,8。该语句主要用来给存储区定
位的,即允许程序设计者将指令定位在特殊的字边界上。如在半字、全字或双字边界上对齐。
这里 w 的取值必须是 4 或 8,由它指明属于全字范围(w=4)还是双字范围(w=8) 。b 的取
值范围是 0、2(当 w=4)或 0、2、4、6(当 w=8)
,表示要对齐的边界位置。b 和 w 的组合
含义如下表所示:
b,w 说明
0,4 一个字的开始处
2,4 一个字的中间
0,8 一个双字的开始处
2,8 一个双字的第二个半字
4,8 一个双字的第三个半字
6,8 一个双字的第四个半字
如果指令计数器已经置成了所需的边界则 CNOP 指令不起作用,若为了得到特殊
的边界对齐必须跳过一些半字的话,则汇编程序产生一些空操作指令 BCR 0,0 从而保证
指令流的连续性。例如:若现行地址计数器的值为 00001E 则执行以下语句:
CNOP 2,8
BALR 14,15
汇编后的结果相当于:
BCR 0,0
BCR 0,0
BALR 14,15
由此可见 CNOP 语句用于控制指令计数器的值以对齐存储边界,且跳过的半字填
以空操作指令 BCR 0,0。

4.程序间的结合
前面已经介绍过,当编辑程序时,往往把一个大程序分成几个小程序,或者把一个程序
分成几个程序节,现在的问题是程序与程序之间、程序节与程序节之间如何互相访问它们之
中所定义的名字,以建立段间的联系。入口语句 ENTRY、外部语句 EXTRN、V 型和 A 型
地址常数提供了解决这一问题的能力。
⑴.指定入口点语句 ENTRY
ENTRY 语句是指定该语句所在程序内所定义的符号名被其它程序所引用,这些符
号名称为入口点,语句形式为:
ENTRY 符号名 1,符号名 2,……,符号名 N
其规则是:
①.操作数域中的符号名,必须在该程序中定义;
②.符号名是浮动名,不能在虚拟节和公共节中被定义;
③.如果以 START,CSECT 语句的名作为入口点符号,不必用 ENTRY 语句来定
义。
⑵.定义外部符号名语句 EXTRN
EXTRN 语句是指定该语句所在的程序中需要引用的其它程序定义的符号名,这些
符号名称为外部符号名,其形式如下:
EXTRN 符号名 1,符号名 2,……,符号名 N
其规则为:
①.操作数域中的符号名,不能在该语句所在的程序内作为语句的名字来定义;
②.外部符号名在表达式中作为项使用时只能用一项不能作为二个或二个以上项来

- 33 -
使用;
③.用 V 型地址常数定义的外部符号,无需用 EXTRN 语句来指定;
④.要想得到 EXTRN 语句定义的外部符号地址,用 DC 指令的 A 型常数值定义外
部引用名。

5.符号等价语句 EQU
EQU 语句是给名字域中的记号,赋予值和长度属性等,其格式如下:
名字 EQU 表达式
其规则为:
⑴.EQU 语句在 ICTL 后 END 之前中的任何位置均可;
⑵.在操作数域的表达式中作为项来使用的符号,必须在此语句之前定义;
⑶.表达式的值与长度属性一起赋给此指令的名;
⑷.表达式可以是绝对的,也可以是浮动的。
例如:
PI EQU 31415

6.产生符号表的 LTORG 语句
用汇编语言编写程序时,可以使用符号常数以使编码简单。例如:
CLC HADE(2) ,=C’SM’
L GR5, =F’50’
这些符号常数也是数据需要存放在内存里去,通常汇编程序把这些符号常数积累存放在
汇编程序最后,但有时需要将符号常数积累放在预先给定的地方,否则会出现汇编错误。
例如:
EX1 START
……
L 5, =F’5’
……
MVC TOKYO,=C’AB’
……
LTORG
XY DS 5000C
END
若没有 LTORG 语句则所有符号常数将排在 DS 5000C 指令后面,使它们全部超过相
对地址最大容量(4096)的地址,因而发生汇编错误,因此需要一个做符号常数表语句,使
所有的符号常数积累按需要放在程序中。LTORG 语句就是用来产生符号表的,对源程序进
行汇编时,能够积累所有遇到的符号常数,构成所谓的符号表,其语句形式为:
名字 LTORG
其规则是:
⑴.符号表的开始被调整为双字边界;
⑵.名字域符号名的值为符号表的开始地址,且长度属性为 1;
⑶.符号表设在 LTORG 语句后面;
⑷.符号表的符号常数的数据长度按照能被 8,4,2,1 除尽的方法依次排列成表,即:
D 型常数 8
F 型常数 4
H 型常数 2

- 34 -
B、X、C、P、E 型常数 1
按这样的格式来编表,比较紧凑,没有空白区,从而节省存储量。

7.拷贝语句 COPY
用户用汇编语言编写的程序或数剧组存放在系统库内,汇编时如需要就把程序块或数据
块从库中取出插入到源程序中这一工作叫源编码的拷贝。
其语句形式为:
COPY 符号
其规则为:
语句操作数域中的符号名是系统程序库内的程序块(或数据块)名,此语句的功能是把
操作数指定的系统程序库内相同的数据块取出,并插入到程序中写有 COPY 语句的那个位
置之后。例如:

START START
…… ……
COPY ABC ABC
…… ……
COPY XYZ ……
……
END XYZ
……
END

图 3-5 COPY 语句使用示例图

8.打印控制
为了使打印出来的汇编清单整齐易懂,汇编语言还规定了以下四种控制打印的汇编控制
语句。它们也与目标程序无关仅为了容易阅读。这四种语句分别是:标题语句、换页语句、
空行语句、打印方式语句等。
⑴.标题语句 TITLE
语句形式:
名字 TITLE ‘标题’
其规则是:
①.名字域为 4 个字符以内的英文字母和数字组成;
②.操作分量域中的标题至多可以书写 100 个字符,其标题的内容在每页的标题位
置上打印,直到碰到其它的 TITLE 语句为止;
③.随着新 TITLE 出现,打印机从新的一页开始打印;
④.TITLE 语句可以使用无数次,且语句自身不打印。
⑵.换页语句 EJECT
随着此语句的出现,下一条指令或语句就从新的一页的开头开始打印,其语句格式
为:
EJECT
其规则是:
①.打印位于最后一行而紧跟其后出现此语句时,此语句视为无效;

- 35 -
②.EJECT 语句可使用多次,且语句本身不打印。
⑶.空行语句 SPACE
随着此语句的出现,就空一行或几行,其语句形式为:
SPACE 十进制整数
其规则是:
①.操作分量域若为空白时,作为空一行处理;
②.操作分量域中的十进制整数若大于其页中剩下的行数时作为 EJECT 语句处理;
③.本语句可使用多次且语句本身不打印。
⑷.打印方式语句 PRINT
PRINT 语句是用于控制源程序汇编清单打印方式的,其语句形式为:
PRINT [ON] [,GEN] [,DATA]
OFF NOGEN NODATA
其规则为:
①.操作分量域中的操作分量顺序可以任意指定;
②.操作分量以上表中的第二行为缺省值;
③.各操作分量的含义如下:
ON: 打印程序汇编清单
OFF: 不打印程序汇编清单
GEN: 打印所有由宏指令生成的指令
NOGEN: 只打印宏指令本身,而由它生成的指令不打印
DATA: 打印全部常数(由 DC 定义的)
NODATA: 打印常数的头 8 个字节
④.PRINT 语句放在程序的任何位置均可,打印方式的控制范围从该语句开始直到
下一个 PRINT 语句为止;
⑤.语句可使用多次且语句本身被打印在汇编清单中。

习题三

1. 汇编程序的作用是什么?
2. 请描述汇编程序的工作过程。
3. 什么叫汇编控制指令或伪指令?它们的功能是什么?
4. DS 指令的格式如何?
5. DC 指令的格式如何?
6. START 语句的作用是什么?
7. END 语句的作用是什么?
8. CSECT 语句的作用是什么?
9. DSECT 语句的作用是什么?
10。COM 语句的作用是什么?
11.USING 语句的作用是什么?
12.DROP 语句的作用是什么?
13.ORG 语句的作用是什么?
14.CNOP 语句的作用是什么?
15.ENTRY 语句的作用是什么?
16.EXTRN 语句的作用是什么?

- 36 -
17.EQU 语句的作用是什么?
18.LTORG 语句的作用是什么?
19.COPY 语句的作用是什么?
20.TITLE 语句的作用是什么?
21.EJECT 语句的作用是什么?
22.SPACE 语句的作用是什么?
23.PRINT 语句的作用是什么?

- 37 -
第四章 顺序与分支程序设计

第一节 程序的功能

现代计算机是一种不寻常的设备,毫不夸张地说,它能完成每秒数百万次以上的操作。
如果这些操作以合适的顺序出现,就会得到很有意义的结果,否则这些结果就很难得到。程
序的功能就是指导计算机操作执行的顺序。创建正确指导操作顺序的程序因而就是非常重要
的,如果要成功地利用计算机的话,本教材后续部分将致力于详细描述产生这样程序的过程。
如前所述,本教材描述的语言是一种符号语言,称为汇编语言,这是一个用于IBM S/390
大型机上的汇编语言,从360系列机器开始就有这一语言,程序员的任务就是编写以这一语
言书写的源程序。计算机不能直接执行或完成汇编语言指令,在计算机能够开始完成程序试
图完成的任务之前,每条这样的指令必须翻译成机器语言指令。计算机可以直接运行的机器
语言指令是难于为人所理解的二进制串。完全由机器语言构成的程序称之为目标程序。
在汇编语言程序准备好以后,几条作业控制语言(JCL)指令被加到程序中,产生的程
序通过一个输入设备送给计算机,如卡片阅读机或终端。JCL指令调用汇编程序,将源程序
指令翻译为机器语言指令,从而产生一个目标程序。在源程序的翻译或汇编阶段,一个显示
源程序指令及其机器指令的清单(即汇编清单)被生成。如果在汇编过程中遇到了错误格式
的汇编语言指令,就会在清单中打印错误信息,且在执行汇编生成后的程序时就会失败。然
而,如果没有语法错误,目标程序就会装入计算机的主存,程序的执行就会开始。
源程序没有语法错误不一定能保证产生的目标程序将完成期望完成的任务,程序可能依
然含有汇编编译程序无法检测的逻辑错误。为了改正这样错误,程序员必须准确理解在将汇
编语言指令编码为机器指令时到底牵涉什么。在以后的章节中包含了完成这一翻译的规则,
到目前为止,所要掌握的是汇编编译程序将产生一个目标程序。
假定程序被成功地编译及装进内存。程序的实际运行大致如以下算法描述所示:

步骤1:开始时,程序第一条指令的绝对地址被送入到一个称之为程序状态字PSW的特定寄
存器中。
步骤2:机器从存贮器取出由PSW指示的指令。
步骤3:然后机器更新PSW的内容,使PSW指向下一条指令。
步骤4:机器执行前述取得的指令的操作。如果指令不产生分支转移(分支由类似GOTO之类
的基本操作产生),则转向步骤2。否则,将要转移的分支绝对地址放入PSW,然后
转向步骤2。

上述算法留下了一些问题。例如,程序如何结束等。我们将在以后逐渐地讨论程序的处
理细节。

第二节 几条简单指令介绍

- 38 -
为了介绍基本的程序设计,本节中将介绍几条简单的指令,它们分别属于RR及RX类型
的指令。

1.RR(寄存器-寄存器)类指令
RR指令经常用于涉及两个寄存器的操作。本节中我们介绍寄存器相加、寄存器相减、
寄存器装载指令。
⑴.寄存器相加
AR r1,r2 (加寄存器)
功能: (r1)←(r1)+(r2)

指令的执行使寄存器r2中的内容与寄存器r1的内容相加,结果送入寄存器r1中,而
寄存器r2的内容不变,除非r2就是r1。如果在加的过程中产生了溢出,则会引起程序的终止,
且将打印出错信息。
例如,指令AR 14, 0的执行将使包含在R0中的数(该数可以为正的或负的)与R14的
内容相加,结果存入R14中。
在前面AR指令的符号格式讨论中,给出的是程序员使用的形式。正如前面所提到
的那样,在指令实际执行前,符号形式必须翻译为机器码形式。当程序被执行时,RR类指
令的机器码形式是一个在机器内存中占半个字的16位二进制数。因而,RR类指令的机器码
可以表示为4位十六进制数,RR指令编码的精确形式为:

hohohr1hr2
其中:hoho是2位说明指令目的十六进制操作码
hr1是用做第1个操作数的寄存器的编号(0~F)
hr2是用作第2个操作数的寄存器的编号。
AR指令的操作码为1A。因而,AR 14,0的指令编码为1AE0。1A意味着编码的指
令是一个AR指令,E表示第一个操作数是R14,0表示第2个操作数为R0。
本指令的执行影响条件码CC。
⑵.寄存器相减
SR r1,r2 (减寄存器)
功能: (r1)←(r1)-(r2)

这一指令的执行将用寄存器r1中的数减去寄存器r2中的数。正如AR指令一样,结
果替换了寄存器r1的内容,寄存器r2保持不变,除非r2就是r1。可能产生定点溢出。SR指令
的操作码为1B。因而,SR 14,0的指令编码为1BE0。1B意味着编码的指令是一个SR指令,
E表示第一个操作数是R14,0表示第2个操作数为R0。
本指令的执行影响条件码CC。
⑶.寄存器装载
LR r1,r2 (装载寄存器)
功能:(r1)←(r2)

这一指令的执行致使寄存器r1的内容被寄存器r2的内容替换,而寄存器r2的内容保
持不变。因而,指令LR 5,10的执行将把R10的内容拷贝至R5。LR指令的操作码为18。因
而,LR 1,15的指令编码为1B1F。18意味着编码的指令是一个LR指令,1表示第一个操作数
是R1,F表示第2个操作数为R15。

- 39 -
本指令的执行不影响条件码CC。
其它RR指令在本教材的其余部分介绍。因为最初几个例子将仅采用这三条RR指令以及
一条RX指令,RX指令将在下面介绍。

2.RX(寄存器-变址存储器)类指令
RX指令通常涉及一个寄存器和一个内存单元之间的操作。本节中我们介绍装载指令和
存储指令。
⑴.装载指令
L r, D(X,B) (装载)
功能:(r)←(D+(X)+(B)

本条指令将内存自D(X,B)有效地址开始的全字装载到寄存器r中。r原有的内容被替
换,而内存中的全字保持不变。在装载指令执行的过程中,可能产生三种错误:
①.如果自D(X,B)计算出的绝对地址不是4的倍数(不是全字边界),会产生一个规
格异常错误(specification exception)

②.如果D(X,B)是一个大于任何实际内存地址的地址,会产生一个地址异常错误
(addressing exception)。这个地址异常仅当X或B包含一个异常大的数时发生。
③.如果D(X,B)是一个区域的实际地址,但该地址不在分配给该本程序的存贮区域
内,此时将产生一个保护错误(protection error) 。
一个RX的编码形式占用两个半字(即一个全字),与如下格式一致:

hohohrhx hBhDhDhD
其中: hoho是表示特定指令的操作码
hr是第一个操作数的寄存器号
hx是变址寄存器的编号(省略时为0)
hB是基址寄存器的编号(省略时为0)
hDhDhD是偏移量
由于L的操作码为58,因此L 2,,12(1,10)指令的编码为5821 A00C,58意味
着编码的指令是一个L指令,2表示第一个操作数是R2,1表示第2个内存操作数地址的变址
寄存器为R1,A表示第2个内存操作数地址的基址寄存器为R10,00C表示第2个内存操作数
地址的偏移量为12。注意用于相对地址的偏移必须在范围0到4095以内,因为FFF=4095是
最大的三位十六进制数(在指令的编码形式中只提供了三位十六进制数字的偏移量)。
本指令的执行不影响条件码CC。
⑵.存储指令
ST指令完成的操作正好同L指令的相反。
ST r, D(X,B) (装载)
功能: (D+(X)+(B))←(r)
本指令的执行使寄存器r的内容被D(X,B)位置的全字替换;寄存器r的状态保持不
变。相应于D(X,B)的绝对地址必须在全字边界上。对L指令可能发生的异常也能发生在ST指
令上。
由于ST的操作码为50,因此ST 2,,12(1,10)指令的编码为5021 A00C,50
意味着编码的指令是一个ST指令,2表示第一个操作数是R1,1表示第2个内存操作数地址的
变址寄存器为R1,A表示第2个内存操作数地址的基址寄存器为R10,00C表示第2个内存操

- 40 -
作数地址的偏移量为12。注意用于相对地址的偏移必须在范围0到4095以内,因为FFF=4095
是最大的三位十六进制数(在指令的编码形式中只提供了三位十六进制数字的偏移量)。
本指令的执行不影响条件码CC。

第三节 完整的程序示例

介绍了那么多的准备知识之后,我们正式开始介绍汇编语言程序设计的内容。首先介绍
一个简单的示例程序,读者可以从这个程序学到汇编语言源程序的构造和书写的注意事项。
在看这个样板程序之前,需要介绍S/390操作系统(即OS390)与程序运行相关的两个额外
的细节:
1.当程序开始执行时,R15将包含程序开始的绝对地址,至此,可以假定R15为程序中
第一条指令的地址。这一事实是有意义的,因为所有相对地址可以通过使用R15作为基址寄
存器创建;
2.当程序执行开始时,R14包含程序执行完时要转移的返回绝对地址。这意味当程序
结束时,一个转向R14中地址的分支是有效的,这种转移称为从程序退出。产生退出的分支
指令格式为(分支的有关指令和分支程序设计我们将在下一章介绍):
label BCR B'1111',14
下面的程序4-1是一个完整的示例程序:

ADD2 CSECT
* 本程序从程序的第 5 个和第 6 个字取两个数相加
* 结果存放在程序的第七个字
L 1,16(,15) 将第1个数装入R1(4字节RX指令)
L 2,20(15) 将第2个数装入R2(4字节RX指令)
AR 1,2 将R2加到R1上(2字节指令RR)
ST 1,24(15) 将第七个字结果存入R1
BCR B'1111', 14 退出程序(2字节指令RR)
DC F'4'
DC F'6'
DS F
END ADD2
程序4-1

这是出现在本教材中的第1个程序,我们将进行仔细分析:。
1.语句ADD2 CSECT是程序如何开始的例子。名ADD2是任意的;然而,选择一个
同程序目标相联系的名是一个好办法;
2.以下两个语句为注释。第1列具有*号的任何输入记录都是注释,注释不会产生任何
代码,注释本身可以从2列到71列的任何一个位置开始。一个给出程序目标及总体信息(检阅
程序员可能发现很有用)的注释段总是出现在一个程序的前面;
3.本程序中能翻译为可执行代码的指令为:
4. L 1,16(1,5)
L 2,20(15)

- 41 -
AR 1,2
ST 1,24(15)
BCR B'1111',14
汇编编译程序将这些指令翻译为机器代码并产生如下形式的清单。
LOC OBJ CODE SOURCE STATEMENT
000000 5810 F010 L 1,16(,15)
000004 582F 0014 L 2,20(15)
000008 1A12 AR 1,2
00000A 501F 0018 ST 1,24(15)
00000E 07FE BCR B'1111',14
在第1条RX指令中,R15用作基址寄存器;在另一条RX指令中,没有使用基址寄存
器,R15被用作变址寄存器。当D(X,B)中两个寄存器只有1个说明时,该寄存器用作X或B对
程序执行来说没有区别;
4.常量生成在000010开始的位置,从DC和DS语句产生的清单为:
LOC OBJ CODE SOURCE STATEMENT
000010 00000004 DC F'4'
000014 00000006 DC F'6'
000018 DS F
注意DS语句没有生成任何目标代码;
5.END语句标志程序的结束,包含一个开始标号作为操作数。通常,这是用在CSECT
语句中的标号。

第四节 程序的改进

程序4-1实现的功能是从程序的第5个和第6个字取两个数相加并把结果存放在程序的第
七个字。从功能上来说,已经完成了要求,但是这个简单的程序存在着几个明显的缺点:第
一、加法的结果没有显示出来,用户不知道程序运行的结果是什么;第二、程序中的地址全
部是显式地址,所有地址必须要由程序员手工计算,这个工作比较枯燥和乏味,另外,一旦
修改程序,所有地址都要重新计算一次。本节中将对这两个缺点加以改进。

1.内存转储指令XDUMP
XDUMP语句是一条由ASSIST定义的宏指令,当ASSIST汇编程序进行符号指令编码时
可以利用这一指令。XDUMP指令的格式为:

label XDUMP area, length

第一个操作数必须是一个要在指定输出上显示的存贮区域的D(X,B)地址,第二个操作数
指示要显示的存贮区域的字节数。XDUMP语句的功能是在指定的输出上显示给定区域的内
容以及某些解释信息。如果两个操作数都省略,就会显示16个寄存器的内容而不是存贮区域
的内容。因而,指令
XDUMP 12(15),4
将引起4字节的从12(15)地址开始的存贮区域显示在指定输出上。另一方面,指令:

- 42 -
XDUMP
将输出所有通用寄存器的内容。
XDUMP指令的主要用途是作为调试工具在程序中对错误进行定位。读者将很快学会用
这一工具跟踪程序的执行过程以及验证程序各段产生的结果。而现在我们用它来显示程序的
最终输出结果。处理输入输出的大部分技术将在本章后面的几节介绍。

2.隐式地址的使用
在程序4-1这样的小程序中,程序员计算程序中的所有相关地址工作量尚不太大,然而
在一个大程序中,完成这些计算将是非常乏味的,并且程序的可读性大大下降。这就激发了
使用一个不同形式的相对地址的想法,称为隐式地址。一个隐式地址是一个将由汇编程序转
换成显式地址的地址。
同RX指令一起使用的隐式地址的两个最普遍的形式为
label

label(r)
其中: label是程序中一个域的标号
r 是一个寄存器编号
当只用了一个标号时,隐式地址涉及给定标号的域。因而,代码
L 1,WORD

WORD DS F
解释了第一种形式的隐式地址的使用。如果在隐式地址中使用寄存器,则有效地址为将寄存
器内容和指定域地址相加而来的地址。因此,假定R10包含八个值0、4、8、…、28之一,
则代码:
L 1, WORD(10) (编码后使用基和索引寄存器)

WORD DS 8F
指令将WORD DS 8F产生的8个字中的一个字装入R1,是哪个字将由R10的内容决定,在
这种情形下,R10起着变址寄存器(即多用作对一系列域的索引)的作用。
如下形式的隐式地址也可以使用:
Label ± n
Label ± n (r)
其中: n是一个十进制数
r是寄存器编号
例如,AREA+10(2)表示通过将R2的内容加AREA后10个字节地址得到的地址。正如前
面提到的,当使用了隐式地址时,汇编程序必须将其转为相应的显式地址。因此,对汇编程
序来说,源程序中必须提供以下信息:
⑴.哪个寄存器用作基址寄存器;
⑵.基址寄存器中的绝对地址是什么
例如,程序4-1中当程序开始执行时,R15包含的ADD2的绝对地址必须告知汇编程序,
这一信息可以通过使用USING语句传达给汇编程序,语句的形式为:
USING Label, reg
其中: Label是一个域的标号
reg是寄存器编号,假定程序执行期间包含那个域的绝对地址。

- 43 -
这些概念较复杂,请读者阅读程序4-2。这一ADD2的重写版本利用了XDUMP和隐式地
址,从而使得程序4-1的可读性和可维护性的到了很大的提高。

ADD2 CSECT
USING ADD2,15
L 1,WORD1 装入WORD1到R1
L 2,WORD2 装入WORD2到R2
AR 1,2 得到两数的和并
ST 1,WORD3 存贮到WORD3
XDUMP WORD3,4 解出答案
BCR B'1111',14 从程序退出
WORD1 DC F'4'
WORD2 DC F'6'
WORD3 DS F
END ADD2

程序4-2

注意如下两个改进:
⑴.USING语句告知汇编编译程序,当它把隐式地址转化为显式地址时,它可以使用
R15作为基址寄存器,并可以假定当程序执行时R15包含ADD2的绝对地址;
⑵.XDUMP将存贮在WORD3的内容在指定输出上显示。

第五节 条件码的设置与分支程序设计

在目前谈及的可执行指令(ST、L、AR、SR、LR、XDUMP)中,AR和SR是唯一改变条
件编码的指令。条件编码CC是一个2位二进制数。某些指令将CC的值设定为特定的值,以
反映指令产生的结果。例如,AR和SR的执行将设置CC如下表所示:
CC 含义
0 操作的结果为0
1 操作的结果<0
2 操作的结果>0
3 发生了溢出
因而,假定R1包含00001AFC而R2包含001247AE,以下指令的执行:
AR 1,2
将使CC的值置为2,表示在R1中有一个正的结果。
CC实际上是称为程序状态字PSW的64位单元的一部分。在本教材的前面内容中我们曾
指出PSW总是含有下一条要执行指令的地址。而CC占据位34和35(从左数起,开始为0),下
一条指令的地址保存在位40~63中。
程序员可以利用CC确定程序执行的流向。这一机制通过条件分支BC(Branch on
Condition)指令产生,BC指令依据CC的值产生或不产生分支。如果产生了分支,下一条要

- 44 -
执行的指令将从要转移的分支的地址开始,而非紧接BC之后的指令。如果分支指令无效,
紧接其后的指令是下一条要执行的指令。BC指令的形式为:

label BC B'mask', addr

其中第二个操作数以D(X,B)的形式给出,为产生分支要继续执行的地址。第一个操作数
用于指定要产生分支的CC设置,CC有4种可能的设置,分别为0、1、2和3。屏蔽mask是一
个4位二进制数;自左开始的每一位相应于CC可能的值之一。当执行BC指令时,仅当mask
中的1位数对应于当前CC设置才产生转移。例如,如下指令执行时:
BC B'1011', LOOP
如果此时CC的设置为0、2或3,将转移到分支LOOP。注意mask为’1111’将使分支在所有状
态下都产生(即无条件转移)。例如,如下指令的执行:
BC B'1111', READ
将无条件转移到具有标号READ的指令。
BC指令的编码格式为:

hohohmhX hBhDhDhD
其中: hoho为指令操作码
hm为mask
hX hBhDhDhD为通常的用于RX指令的D(X,B)地址编码
由于BC指令的操作码为47,所以指令BC B'0001',12(3,4) 将 被 汇 编 程 序 编 码 为 4713
400C,程序4-3解释了BC指令的使用。假定程序员准备敲入程序运行,他将为W1和W2填入
他自己的值。

* 这一程序打印两个数中较大者
* 这两个数在W1和W2中
MAX CSECT
USING MAX,15 R15指向程序头
L 1,W1 装第1个字到R1
L 2,W2 装第2个字到R2
SR 1,2 如W1大,则R1将为正
BC B'0010', ONE 如W1大,转移到ONE
XDUMP W2,4 打印出W2
BC B'1111', GO 转移到退出
ONE XDUMP W1,4 打印出W1
GO BCR B'1111', 14 退出
W1 DC F'任何数'
W2 DC F'任何数'
END MAX

程序4-3

在程序4-3中,如果CC在执行SR指令时为2,就会产生到ONE的转移。因此,到ONE的
转移仅当减法的结果为正时才会产生,这相应于W1包含一个比W2大的数的情况。

- 45 -
象许多RX指令一样,BC指令有一个相应的RR类指令,即BCR(寄存器条件转移)指令,
其指令格式如下:

label BCR B'mask', r

其中: mask 同BC指令


r为包含转移地址的寄存器的编号
因此
BCR B'1111',14
无条件地转移到R14包含的地址。因为R14总是包含一个程序开始执行时的出口地址,上述
指令被用于一个程序的正常终止。

第六节 数据的输入和转换

前面几节中的程序4-1、4-2、4-3中的数据都是事先作为常数定义在程序当中的,然而,
在大多数实际应用中,数据应该是在程序运行时由用户通过终端输入或通过其它输入设备进
行输入的。本节将讨论数据的输入即转换问题。

1.数据输入XREAD指令:
在如下讨论中,我们假定要用XREAD输入的记录长度为80字符。这是由历史原因造成
的,在IBM汇编程序使用逐渐普及期间,穿孔卡片是程序编码的介质,这种卡片规定了每行
最多只能有80个字符。输入记录通常是某些固定的长度。在本书中,大部输入记录包含直到
80个字符的数据。当这些字符读入内存后,每个字符要占用一个字节。每个输入记录的一个
字符可以存贮EBCDIC编码规定的256个合法的值之一。当记录读入内存时,每一个可能的
值转换成一个唯一的EBCDIC编码。当用穿孔卡片作输入时,每列可有256个不同的穿孔模
式,每种模式相应于从00到FF的十六进制值。今天虽然不再使用穿孔卡片作为输入介质了,
但是这些规定却保留了下来。大部分输入记录仅包含“可读字符”,这些字符为可能的256
个值的子集。“可读字符”与内存中产生的十六进制值的对应。图4-1给出了EBCDIC编码集
的一部分:

穿孔 表示 存贮编码
无 空白 40
11-4 * 5C
12-1 A C1
12-2 B C2
0 0 F0
1 1 F1
2 2 F2
3 3 F3
4 4 F4
5 5 F5
6 6 F6
7 7 F7
8 8 F8

- 46 -
9 9 F9

图4-1 EBCDIC编码表(部分)

例如,如果输入在前4个字符中包含1234的一个记录到名为CARD的内存区域,在CARD
的前4个字节中将会是什么?
CARD的第1个字节将包含相应于’1’的值,因此CARD的第一个字节为F1;第2个字节将
为F2;第3个为F3;第4个为F4,因此CARD的前4个字节将包含F1F2F3F4。读者应注意到得
到的是EBCDIC编码的字符形式。
XREAD指令是一个可被用于读一个输入记录到存贮区域的ASSIST宏指令。XREAD的
指令格式为
Label XREAD area,length

其中:area 是D(X,B)形式的地址,该地址为要输入区域的首址。
length是要读入的字符数。长度应小于等于80。如果小于80,输入记录的剩余字
符将被忽略。
CC在XREAD执行后的设置传递了如下信息:
CC 含义
0 读记录成功
1 无记录可读,这是称为文件结束的状态
2 - (CC从不设置为该值)
3 -
读到内存中的数据以称之为字符格式的形式出现。特别地,十进制数字的字符表示为
F0,F1,F2,…,F9。假定一个记录在第1列包含4,第3列包含3,其它地方均为空格,则
读入到内存中的记录的映像为:
F440F3404040…
数据读入以后,现在假设我们要对第1和第3字节表示的数做加法。立即就会遇到两个困
难:
⑴.F4和F3不能以他们的字符形式相加。他们首先就被转换为相应的二进制数;
⑵.二进制数必须送到寄存器中,因为加法必须用一条AR指令完成。
怎样解决这两个问题呢?这就必须用到输入数据的转换指令XDECI。

2.输入数据的转换XDECI指令
XDECI是用于转换内存中字符形式表示的数为相应的在寄存器中存放的二进制数的
ASSIST宏指令。XDECI的指令格式为:
label XDECI r, addr
(能转换的最大的正数=2147483647)

其中: r为要存放二进制数于其中的寄存器的编号
addr是字符格式数据的D(X,B)地址。
XDECI的执行具有如下效果:
⑴.从addr指定的位置开始,扫描内存寻找第一个非空格字符;

- 47 -
⑵.如果第一个找到的字符是除十进制数字或+、-号以外的任何字符,R1设置为该
字符的地址,条件编码设置为3(溢出)以显示无十进制数可转换。r的内容不变,
且不再做其它事情;
⑶.如果第一个字符为+、-号或十进制数字1到9,则该数字被扫描,数据被转换成二
进制并存放在r中;
⑷.R1被置为十进制数字串后第1个非数字的地址。因而,r通常不要用1。这允许用户
扫描任意一个其间以空格分开多个的十进制数的值;
⑸.如在空格前发现了十个或多于十个的十进制数字,R1被设置为第一个非十进制数
字的地址,CC设置为3,r保持不变。一个单独的+号和-号引起类似的动作,R1设
置为紧接符号后的字符地址;
注意XDECI的执行改变了R1。因此,如果在调用XDECI指令之后程序员还要想引用R1
中的原值,则必须做好保存工作。
CC 由 XDECI 设置如下:
CC 含义
0 转换的数为0
1 转换的数<0
2 转换的数>0
3 试图转换一个无效的数
应该指出的是,XDECI执行进行的扫描或许会超出应扫描的域范围,直到遇到一个非
空字符为止。因此,如果要扫描输入记录,最好用一个非空字符而不是十进制数字来标志数
据结束,以便可以检查CC以确定何时到达数据的尾部。
例如,这可以用以下的代码做到:
CARD DS CL80
DC C'*'
程序4-4读入两个记录,每个记录有两个数,计算两个数的差并用XDUMP出输出。

******************************************************************************
*这一程序读入数(每个纪录2个数据),并DUMP出这两个数的差
******************************************************************************
*
DIFF CSECT
USING DIFF, RIS
*
XREAD CARD,80 读第一个纪录
*
LOOP BC 'B0100', EXIT 若所有纪录读完,退出
XDECI 2,CARD 转换该纪录中的第一个数据
BC B'0001', GETNXT 若转换不成功,跳过此纪录
XDECI 3,0(1) 转换该纪录中的第二个数据
BC B'0001', GETNXT 若转换不成功,跳过此纪录
*
SR 2,3 计算差值
*
ST 2, WORD 保存并

- 48 -
XDUMP WORD,4 显示差值
*
GETNXT XREAD CARD,80 读下一个纪录
BC B'1111', LOOP 继续循环
*
EXIT BCR B'1111', 14 退出程序执行
*
CARD DS CL80 数据输入区域
WORD DS F 存放差值区域
END DIFF

程序4-4

第七节 数据的输出及转换

前一节中我们讨论了数据的输入即转换。从中我们可以看到,数据输入到内存时是一字
符形式存放的,经过转换后得到二进制形式的数据,然后可以送入寄存器中去参与运算。数
据的输出过程刚好与此过程相反,首先应把寄存器中的二进制数据转换为字符形式存放在内
存中,然后再输出,当然,如果数据本身就是字符形式的,那么转换过程可以不必做。

1.字符数据输出XPRNT指令
正如从一个记录输入的数据存贮为字符格式一样,要打印的数据也是一样。在打印一行
时涉及两个步骤:
⑴.要打印的行必须在内存的一个区域中构造。实际要出现在打印行中的字符从该区域
的第2个字节开始。最开始的字节保留作打印控制字符,控制打印的行在页面上的位置。可
以用在第1个字节的编码及其控制含义为:
空格 打印前换行
0 打印前换两行
1 在打印前跳到下一页开始
整个打印行(包括打印控制字符)必须小于等于133个字符,因为在大部分宽行打
印机上只有132个字符的打印位置。
⑵.在打印行构造出来后,可以使用XPRNT指令打印该行,XPRNT的指令格式为

label XPRNT addr, length

其中:
addr是打印行在内存中的D(X,B)地址。
length是要送到打印机去的记录字节数。
程序4-5解释了XPRNT指令的使用,该程序读任意个数的记录,每个记录放入一个打印
行,第一行将打印在页的顶部,所有后续的行将空一行打印。

***************************************************************************
* 该程序读入数据并打印直到输入数据结束

- 49 -
***************************************************************************
*
PRINT CSECT
* USING PRINT,15
XREAD CARD1,80 读第一个纪录
BC B'0100',EXIT 若纪录为空则退出运行
XPRNT CC1,81 打印第一个纪录
XREAD CARD2,80 读第二个纪录
LOOP BC B'0100',EXIT 若纪录为空则退出运行
XPRNT CC2,81 打印当前纪录
XREAD CARD2,80 继续读下一纪录
BC B'1111',LOOP 继续循环
EXIT BCR B'1111',14 退出程序运行
*
* 从一个程序只有一个出口是一个好的习惯
* 这就是为什么我们总是转向出口13618021698
*
CC1 DC C'1' 此控制字符将引起打印换页
CARD1 DS CL80 输入数据空间一
CC2 DC C'0' 此控制字符将引起打印留两空行
CARD2 DS CL80 输入数据空间二
END PRINT

程序4-5

2.输出数据的转换XDECO指令
仅知道打印字符串不足于使计算结果显示出来。先前将字符转换为二进制的问题通过
XDECI解决了。现在涉及到相反的问题。
假定结果已存放在寄存器中,现在希望将结果输出到打印机上。这涉及从寄存器取数据,
将其从二进制形式转换为字符表示形式,并将此形式存放到打印行的存贮区域里面。所有这
些可以通过使用XDECO指令简单地完成。该指令的格式如下:

label XDECO reg, addr

其中:
reg为存放二进制数于其中的寄存器的编号
addr是字符格式数据的D(X,B)地址。
这一指令的执行将第一个操作数给出的寄存器中的数转换为一个12字节的字符表示形
式,并存贮在由第二个操作数D(X,B)地址给出的区域中。寄存器的内容不变。例如,如下指
令的执行:
XDECO 10,ANSWER
将把R10中的二进制数转换成字符格式,且其结果存贮在从ANSWER开始的12字节区域中,
R10的内容不变。

- 50 -
XDECO将用空格进行填充12字节域中的数字首部而向右对齐。如果数为负数的话,一
个负号将打印在第一位有意义数字的左部。程序4-6解释了XDECO的使用,它从每个输入纪录
读两个数并打印出这两个数的和。

*********************************************************************************
* 本程序从输入纪录读数,每纪录含两个数。两个数的和被打印出来 ㊣
*********************************************************************************
SUMUP CSECT
USING SUMUP,15
XPRNT HEAEING,28 打印页头
XREAD CARD,80 读入数据
CHECKEOF BC B'0100',EXIT 若无数可读退出
XDECI 2,CARD 转换两个输入数据
XDECI 3,0(1) (假定数据都是合法的)
AR 2,3 计算两数之和
XDECO 2,OUTPUT 转换和为字符形式
XPRNT CRG,13 打印结果
XREAD CARD,80 读下一数据
BC B'1111',CHECKEOF 继续循环
EXIT BCR B'1111',14 退出程序运行
CARD DS CL80 输入数据空间
CRG DC C' ' 此字符控制打印一空行
OUTPUT DS CL12 输出数据转换空间
HEADING DC C'1 THIS IS THE OUTPUT OF SUMUP'
END SUMUP

程序4-6

第八节 更多的指令和程序示例

从前几节中介绍的一些指令我么可以看到,除了指令要求寄存器或者是内存中的一个字
作为第2操作数不同外,许多RR指令都有一个执行等价操作的RX指令。例如,LR和L指令
完成相似的操作:LR第2个操作数从一个寄存器取,而L第2个操作数从内存访问一个全字。
本节中我们将介绍与AR、SR对应的A、S指令,并介绍其它一些指令,例如:C和CR、LCR、
LNR、LPR、LTR等等。

1.加法指令A
加法指令的格式为:
A r, D(X,B) (加法)
功能:(r1)←(r1)+(D+(X)+(B)

- 51 -
这一指令的执行将把D(X,B)给出地址开始的全字加到r寄存器的中,内存中全字的内容
不变,结果替换了r的内容。例如,指令
A 2,WORD
执行后将WORD的内容加到R2的中。如果D(X,B)解析到其它而非4的倍数(即全字边界)的地
址,就会产生一个规格异常错误(specification exception)。如果D(X,B)指示到程序边界之外
的字,会产生一个保护错误(Protection error)或者地址异常错误(Addressing error)。如果
加法的结果太大或太小而不能装入r,一定会产生溢出。
A指令的机器语言形式与前面介绍过的L指令相同,指令操作码为5A,指令的执行影响
条件码CC。

2.减法指令S
减法指令的格式为:
S r, D(X,B) (减法)
功能:(r1)←(r1)-(D+(X)+(B)

对应于SR指令,正如A指令对应于AR指令一样,对S的描述正如同对A的描述一样,除
了要从r的内容中减去而不是加第2个操作数以外。
S指令的机器语言形式与前面介绍过的L指令相同,指令操作码为5B,指令的执行影响
条件码CC。
有两条指令的功能与SR和S类似,它们是CR和C,相同指出是它们(四者)都是做减法,
不同之处在于CR和C减的结果并不送入第一操作数(即寄存器)中,仅仅利用减的结果去影
响条件码CC,因此它们可以用于比较两个数的大小。

3.寄存器比指令CR
寄存器比较指令的格式为:
CR r1,r2 (寄存器比较)
功能:
(r1)-(r2)
,结果设置CC

作为这一指令的执行结果,条件码根据如下规则设定:
CC 含义
0 r1和r2中的内容相等
1 r1中的数<r2中的数
2 r1中的数>r2中的数
3 -(从来不会产生)
S指令的执行不会出现任何错误。其机器语言形式与前面介绍过的RR类指令相同,指令
操作码为19。

4.比较指令C
比较指令的格式为:
C r,D(X,B) (比较)
功能:
(r1)-(D+(X)+(B))
,结果设置CC

- 52 -
这一RX指令除自D(X,B)开始的全字被用作第2个操作数外,其功能同CR指令相同。正
如在其它RX指令中一样,保护、地址异常和规格异常错误均可能产生。本指令的机器操作
码为59。

程序4-7解释了A和CR指令的使用。程序读入若干数并打印如下统计信息:
⑴.读入数的个数;
⑵.读入的最小的数;
⑶.读入的最大的数

***********************************************************************
* 本程序读入数据流,每个纪录包含一个数。程序打印最小的值、
* 最大的值以及流中数据的个数。如果没有输入纪录,或者第一纪
* 录没有有效的数,则打印错误信息并退出。程序逻辑如下:
* 步骤1:试读第1个纪录,成功转步骤3;
* 步骤2:打印“空输入”信息,并转步骤12从程序退出;
* 步骤3:从输入纪录上获取数据,成功转步骤5;
* 步骤4:打印“第1个数据无效”信息并转步骤12从程序退出;
* 步骤5:使用第一个值作为目前最小和最大的值,并设读入
* 数的个数为1;
* 步骤6:读下一纪录;
* 步骤7:测试文件结束(循环的结束条件);
* 步骤8:试着从纪录上读数。如果能读则计数并转步骤9。否则转步骤
* 10读下一张卡;
* 步骤9:如果数值小于目前最小的数,用其作为新的目前最小的数。
* 同样,如果该值比目前最大的数还大,将其作为新的目前
* 最大的数;
* 步骤10:试着读下一张卡并返回到循环顶部(步骤7);
* 步骤11:打印3个数值(计数,最小及最大值);
* 步骤12:退出程序。
***********************************************************************
*
MIN@MAX CSECT
USING MIN@MAX,15
*
***<步骤1> 读第一个纪录
*
XREAD CARD,80
BC B'1011',GOTCARD 如有纪录则转移
*
***<步骤2> 打印“空输入”信息
*
XPRNT EMPTYMSG,16
BC B'1111',EXIT
*

- 53 -
***<步骤3> 从输入纪录中获取数据
*
GOTCARD XDECI 2,CARD
BC B'1110',GOT1ST
*
***<步骤4> 打印“第1个数据无效”信息
*
XPRNT BADIST,23
BC B'1111',EXIT
*
***<步骤5> 初始化最小值、最大值和计数器值
*
GOT1ST LR 3,2 R3存放到目前为止最大值
LR 4,2 R4存放到目前为止最小值
L 5,ONE R5存放读入数据个数值
*
***<步骤6> 读下一纪录
*
XREAD CARD,80
*
***<步骤7> 循环执行直到数据结束
*
CHKEOF BC B'0100',EOF
*
***<步骤8> 读出数据,若非法,跳过,非则计数
*
XDECI 2,CARD
BC B'0001',READNXT
A 5,ONE 计数器加1,因为数据有效
*
***<步骤9>
*
CR 2,3 是否新的最大值?
BC B’1100’,CHKMIN 若<=旧的最大值,跳过
*
LR 3,2 否则,设为新的最大值
BC B'1111',READNXT 读下一纪录
*
CHKMIN CR 2,4 是否新的最小值
BC B'1010',READNXT 若>=旧的最小值,跳过
LR 4,2 否则,设为新的最小值
*
***<步骤10> 读下一条纪录
*

- 54 -
READNXT XREAD CARD,80
BC B'1111',CHKEOF 继续循环
*
***<步骤11> 打印结果
*
EOF XDECO 5,#READ 把读入数据个数转换后放入打印行
XPRNT LINE1,28 并打印
*
XDECO 3,LARGEST 把最大值转换后放入打印行
XPRNT LINE2,29 并打印
*
XDECO 4,SMALLEST 把最小树转换后放入打印行
XPRNT LINE3,28 并打印
*
***<步骤12> 退出程序运行
*
EXIT BCR B'1111',14
*******************************************************************************************
CARD DS CL80 数据输入区域
EMPTYMSG DC C'1***EMPTY INPUT'
BADIST DC C'1***INVALID IST NUMBER'
ONE DC F'1'
LINE1 DC C'1#OF CARDS READ'
#READ DS CL12
LINE2 DC C'OSMALLEST VALUE='
SMALLEST DS CL12
LINE3 DC C'OLARGEST VALUE='
LARGEST DS CL12
END MIN@MAX

程序4-7

以下介绍指令LCR、LNR、LPR和LTR,它们同LR指令类似。在这四条指令中,LTR是
最有用的。

5.装载补码指令LCR
装载补码指令LCR的格式如下:
LCR r1,r2 (装入补码)
功能(r1)← -(r2)

本指令将把r2的补码装入r1。除非r2和r1为同一个寄存器,否则r2将保持不变。如果r2
在执行前包含n,r1将包含-n。本指令的执行影响条件码CC,CC设置如下:
CC 含义
0 r1包含0

- 55 -
1 r1包含一个负数
2 r1包含一个正数
3 溢出
LCR指令的操作码为13。

6.装载负数指令LNR
装载负数指令LCR的格式如下:
LNR r1,r2 (装入负数)

功能(r1)← -|(r2)|

本指令将r1的内容替换为r2的内容的绝对值的负数。除非r1就是r2,否则r2的内容保持
不变。本指令的执行影响CC,CC设置如下:
CC 含义
0 r1包含0
1 r1包含一个负数
2 —
3 —
LNR指令的操作码为11。

7.装载正数指令LPR
装载正数指令LCR的格式如下:
LPR r1,r2 (装入正数)

功能(r1)← |(r2)|

本指令的执行将r2的内容的绝对值替换r1的内容,r2的内容不变(除非r1就是r2)。如果r2
包含最大负数80000000,将产生定点溢出。本指令的执行影响条件码CC,CC设置如下:
CC 含义
0 r1包含0
1 -
2 r1包含一个正数
3 产生了溢出
LNR指令的操作码为10。

8.装载测试指令LTR
装载测试指令LTR的格式如下:
LTR r1,r2 (装入测试)
功能(r1)←(r2)
,并设置CC

本指令的执行将r2的内容装入r1并测试该值,根据该值设置条件码CC。r2的内容不变。
因此,除了设置CC外,这条指令同LR指令相同。本指令的执行影响条件码CC,CC设置如
下:
CC 含义

- 56 -
0 r1包含0
1 r1包含一个负数
2 r1包含一个正数
3 -
LTR指令的操作码是12。
LTR是一条可用来测试一个寄存器是否包含0。例如,为了在R12包含0的情形下转向
ZERO分支,将使用如下两条指令:
LTR 12,12
BC B'1000',ZERO
由于R12装入自己的内容,因此唯一的效果就是设置CC。
程序4-8利用了LPR指令取一个数的绝对值。它读入一些数(每个记录一个数),并计算三
个数的和、绝对值的和、正数的和以及负数的和。

***********************************************************************
* 本程序读入一组数据纪录,每个纪录包含一个有效的数,程序累计
* 所有正数的和
* 所有负数的和
* 以及所有数的绝对值的和
* 当检测到EOF时,程序打印一行显示三个和,逻辑如下:
* 步骤1:设置(正数、负数和绝对值的)三个累加和为0
* 步骤2:试读第一个纪录
* 步骤3:检查循环结束条件EOF,结束时转到步骤6打印结果
* 步骤4:将纪录中的数加至合适的累加和
* 步骤5:试读下一个纪录并转到循环顶部(步骤3)
* 步骤6:打印结果
* 步骤7:退出程序运行
***********************************************************************
SUM3 CSECT
USING SUM3,15
*
***<步骤1> 对累加和清零
*
SR 3,3 R3存放正数的和
SR 4,4 R4存放负数的和
SR 5,5 R5存放绝对值的和
*
***<步骤2> 试读第1个纪录
*
XREAD CARD,80
*
***<步骤3> 测试循环结束
*
CHKEOF BC B'0100',EOF
*

- 57 -
***<步骤4> 累加
*
XDECI 2,CARD
BC B'0100',NEG 若为负,则转移
*
AR 3,2 累加正数
BC B'1111',ADDABS 跳转到累加绝对值
*
NEG AR 4,2 累加负数
LPR 2,2 转换为正数
*
ADDABS AR 5,2 累加绝对值
*
***<步骤5> 试读下一张卡转向循环顶部
*
XREAD CARD,80
BC B'1111',CHKEOF
*
***<步骤6> 打印结果
*
EOF XDECO 3,SUMPOS 把正数和转换置入打印行
XDECO 4,SUMNEG 把负数和转换置入打印行
XDECO 5,SUMABS 把绝对值和转换置入打印行
XPRNT ANSWER,132 打印结果
*
***<步骤7> 退出
*
BCR B'1111',14
*
******************************************************************************************
*
ANSWER DC C' 1THE SUM OF THE POS NUMBERS='
SUMPOS DS CL12
DC C' THE SUM OF THE NEG NUMBERS='
SUMNEG DS CL12
DC C' THE SUM OF THE ABSO VALUES='
SUMABS DS CL12
*
CARD DS CL80 输入数据区域
*
END SUM3

程序4-8

- 58 -
9.存储字符指令STC
存储字符指令STC的格式如下:
STC r,D(X,B) (存贮字符)
功能:(D+(X)+(B)) ← (r)最右边字节

指令的执行将引起r的最右边的字节(第24~31位)存贮到D(X,B)指示的内存字节中。因
D(X,B)不必是全字边界,因而不会产生全字边界错误。然而,如D(X,B)超出了STC指令所在
的程序的边界,就会产生一个保护错误或地址异常错误,本指令的操作码为42,指令的执行
不会改变条件码。
例如:如下指令的执行
LA R2,64
STC R2,BLANK
将把一个空格存放到BLANK标号标识的域的第一个字节。

10.装载字符指令IC
装载字符指令IC的格式如下:
IC r,D(X,B) (装载字符)
功能:(r)最右边字节 ← (D+(X)+(B))

指令的执行可用于将单个字节从内存转移到寄存器最右边的字节,寄存器由第一个操作
数指定,内存字节的地址由第二个操作数指定,本指令的操作码为43,指令的执行不影响条
件码。可能产生的错误是保护错误和地址异常错误。注意,r左边的三个字节不被改变。
例如,如下指令的执行:
IC R14,FLAG
R14最右边的字节被FLAG内容替换。

第九节 乘法和除法

本节中我们讨论乘法和除法指令。我们将介绍RR及RX类的指令。

1.寄存器乘法指令MR
每个寄存器内存放的是32位的二进制数,相乘之后得到的是64位的积,一个寄存器显然
存放不下,怎么办?显然需要用两个寄存器来存储,通常是连续的两个寄存器,称为寄存器
对。
寄存器乘法指令MR的格式为:
MR r1,r2 (寄存器乘法)
功能:(r1,r1+1)←(r1)*(r2)

指令中第1个操作数r1必须是一个偶数寄存器,它实际上指定了一个偶/奇寄存器对。被
乘数从寄存器对的奇数寄存器取。因而,如果r1为4,被乘数从寄存器R5取。乘数从r2取。
指令执行的结果使被乘数和乘数的积形成一个64位有符号整数。该积将替换由r1指定的寄存
器对的内容。r2的内容保持不变,除非r2是寄存器对中的寄存器之一。

- 59 -
本指令的操作码为1C,指令的执行影响条件码CC。
例:假定:
R1的内容为00000004
R2的内容为0000FFFF
R3的内容为00000005
则如果执行MR 2,1,R3中的被乘数将被R1中的乘数相乘。结果将替换R2/R3寄存器对
的内容,得出:
R1:不变
R2-R3:00000000 00000014 (4×5=1416)
有两点值得注意:
⑴.寄存器对的偶寄存器包含任何数值而非符号位的扩展时,积必为非常大(大于
7FFFFFFF)或非常小(小于-80000000)。因此,如果结果为正的,偶寄存器通常包
含00000000,如果结果为负(且不小于-231),则偶寄存器包含FFFFFFFF(00000000
和FFFFFFFF均为符号扩展);
⑵.r2可以为r1指定的寄存器对中的任何一个。例如,如下指令的执行:
MR 0,1
将用R1的平方替换偶/奇寄存器对的内容(R0-R1)。

2.乘法指令M
乘法指令M的格式为:
M r,D(X,B) (乘法)
功能:
(r1,r1+1)←(r1+1)*(D+(X)+(B))

这一指令除了乘数从D(X,B)开始的全字取值外,同MR没有区别。本指令的操作码为5C,
指令的执行影响条件码CC。
例如R1包含00000001,如下指令的执行:
M R0,TWO

TWO DC F'2'
将用积替换R0-R1的内容(R0置为00000000,R1置为00000002)。正如其它RX指令一样,如
果D(X,B)的解释产生一个错误的地址,可能出现地址异常、保护以及规格异常错误。

3.寄存器除法指令DR
与乘法指令相反,除法的被除数应该是一个64位二进制数,存放在一对寄存器对中,除
数为32位,放在寄存器中。
寄存器除法指令DR的格式为:
DR r1,r2 (寄存器除法)
功能:(r1,r1+1)←(r1,r1+1)/(r2)

第一个寄存器r1指定了一个偶/奇寄存器对,包含被除数;第二操作数r2寄存器包含除数,
执行结果使商替换寄存器对中奇数寄存器的内容,商的符号由通常的算术规则确定,余数替
换寄存器对中偶数寄存器的内容,符号总是同被除数的符号相同。如果商不能表示为32位有
符号整数,就会产生定点溢出。
本指令的操作码为1D,指令的执行影响条件码CC。

- 60 -
使用DR指令时要小心寄存器对的偶数寄存器的内容,因为被除数被看成一个整体的包
含于寄存器对的64位整数。如果被除数足够小以致能包含在寄存器对的奇数寄存器中,必须
完成如下初始化偶寄存器内容的工作之一:
⑴.如果被除数为正数(即奇数寄存器包含一个正数),则偶数寄存器必须设为全零;
⑵.如果被除数为负数,则偶数寄存器必须设置为FFFFFFFF。
以上每一种工作都是将奇数寄存器的符号位扩展到偶数寄存器。作为执行DR指令的例
子,假定:
R2 包含00000000
R3 包含00000007
R4 包含FFFFFFFE(-2)
则,如下指令
DR 2,4
的执行将使寄存器的内容如下:
R2包含符号同被除数相同余数00000001。
R3包含有正确符号的商FFFFFFFD(-3)。
R4 的内容不变

4.除法指令D
除法指令D的格式为:
D r1,D(X,B) (除法)
功能:
(r1,r1+1)←(r1,r1+1)/(D+(X)+(B))

除了除数从D(X,B)开始的全字取外,D指令的功能和DR指令的一样。
本指令的操作码为5D,指令的执行影响条件码CC。
例:
D 2,MINUS2

MINIUS2 DC F'-2'
将在R2和R3寄存器对中产生同前例一样的结果。
正如先前提到的那样,寄存器对中的偶数寄存器必须仔细设置,有一个简单的办法解决
此问题,假定R3包含被除数而R2必须设置为正确的符号扩展,则
M 2,ONE

ONE DC F'1'
因乘1正好将R3中的32位值转换为R2-R3的64位值,从而就完成了符号扩展,这一技术用在
以下样例程序中。
程序4-9解释了乘法和除法指令的使用,它从每个输入纪录上读入两个数,把两个数相
乘,并把第2个数去除第一个数。然后打印结果。

*******************************************************************************************
* 本程序用于解释乘法和除法,它先读输入纪录,每卡包含两个数。计算且
* 打印两个数的积以及第一个数除以第2个数的商。
* 逻辑如下:
* 步骤1、读第一个纪录

- 61 -
* 步骤2、如果文件结束,转步骤6退出
* 步骤3、从纪录上得到两个数,计算其积和商,假定纪录上的数据有效。
* 步骤4、打印积和商
* 步骤5、读下一个纪录,转步骤2
* 步骤6、退出
*******************************************************************************************
*
MULTDIV CSECT
USING MULTDIV,R15
*
***<步骤1> 读第一个纪录
*
READ XREAD CARD,80
*
***<步骤2> 如文件结束,转步骤6退出
*
BC B'0100',EXIT
*
***<步骤3> 计算积和商
*
XDECI 3,CARD 得到第一个数
*
XDECI 4,0(1) 得到第二个数
*
LR 7,3
MR 6,4 积放入R6-R7寄存器对中
*
* 请注意几乎所有的例程中我们均假定乘积为32位,可以放在奇数寄存器中。
* 只有符合此假定,本程序才能正确地工作
*
LR 9,3
M 8,ONE 符号扩展,做好被除准备
*
* 乘1可以顺利地实现符号扩展到R8中,所以,如果R3中原来是正数,R8中
* 将会存放00000000,否则,将是FFFFFFFF
*
DR 8,4 R9中得到了商
*
***<步骤4> 打印积和商
*
XDECO 7,PR0D
XDECO 9,OUOT 把结果放到打印行
*
XPRNT PLINE,54 打印结果

- 62 -
*
***<步骤5> 读下一个纪录
*
XREAD CARD,80
BC B'1111',TESTEOF 继续循环
*
***<步骤6> 退出
*
EXIT BCR B'1111',14
*
*********************************************************************************************
*
ONE DC F'1' 用于对被除数进行符号扩展
*
PLINE DC C'0' 控制打印两个空行
DC C'PRODUCT IS'
PROD DS CL12 此处放积
DC C' THE QUOTIENT IS'
QUOT DS CL12 此处放商
*
CARD DS CL80 输入数据空间
END MULTDIV

程序2-9

第十节 寄存器等价和扩展助记符

1.寄存器等价的使用
在前一章中我们简单地介绍了符号等价语句EQU,它是一条汇编控制语句。所以,EQU
不生成机器指令。该指令用于指定标号和表达式之间的对应关系。目前,我们介绍如下形式
的EQU的使用:
label EQU expression

其中表达式expression是一个整数。该指令的作用就是建立标号和表达式的一一对应,
以便程序中任何出现标号都以表达式等同的方式处理。
例如下EQU语句应该出现在每个程序中:
R0 EQU 0
R1 EQU 1
……
R15 EQU 15
然后,不管什么地方要访问寄存器,就可使用合适的标号。例如,我们可以使用语句:
L R1,WORD

- 63 -
来代替语句:
L 1,WORD
使用EQU语句的理由在于:汇编程序产生一个随同程序清单的交叉引用表,对每个标
号该表列出了定义标号指令的编号以及使用标号的指令编号。因此,对每个使用的寄存器提
供了现存的参考。当以后我们讨论宏时,将给出一个消除在程序中插入这16个语句的单调而
费时的工作的方法。

2.扩展助记符的使用
汇编程序提供的扩展助记符可以使得我们对条件分支进行编码而不必显式说明其屏蔽
条件。这一特点提供了一个通过助记符说明分支条件的机构,从而简化了编写程序的工作。
通过选择使用助记符,不断检查BC和BCR指令屏蔽含义的工作省去了,程序的可读性大大
提高。如下两组指令解释这一选择项的使用:
⑴.使用传统的 BC 指令
CR 3,4
BC B'1000',ADDR CC=1时则转移,意思是两个数相等转移
⑵.使用扩展助记符
CR 3,4
BE ADDR 相等转移(Branch on Equal )
两组指令汇编后产生了同样的代码,这就意味着
BE addres
正是如下指令的替代物
BC B'1000',addres
本书的附录中包含了一个扩展肋记符表以及相应的BC和BCR指令。注意,每个扩展助
记符倾向用于设置条件编码的莫中特定的指令类。例如BZ LOOP和BE LOOP 都 等 价 于
BC B'1000',LOOP。然而BZ助记符倾向于用在算术操作后,而BE应用在比较指令后。
本教材其余部分的程序设计例子将利用寄存器EQU指令和扩展助记符。

第十一节 符号常数的使用

前一章中我们简单地介绍过符号常数。本节说明它们的使用情况。在日常的程序设计
中,我们经常有必要使用不会被程序改变的常量。例如,如果4要加到R1的内容中,可以使
用如下指令:
A R1,FOUR
……
FOUR DC F'4'
在这种情况下,FOUR是一个从不改变的常量。为了方便,上述两条指令可以由如下一
条指令代替:
A R1,=F'4'
在这种情况下,第二操作数是一个符号常数,它可以看成是前面有等号的常量说明。如
果程序中使用了一个符号常数,汇编程序将自动地生成常量并且在翻译指令时使用产生的常
量的地址。例如
D R2,=F'10'

- 64 -
将引起汇编程序生成一个包含0000000A的全字,并使用该字地址于除法指令的编码中。
字符常量也可被用作符号常数。例如指令:
XPRNT =C'1THIS Prints A header',21
将使字符串打印在页的顶部。这一技术对于打印错误信息和输出题头是方便的。
除非用户指明,否则汇编程序将把符号常数生成的常量紧随程序的结束而存放。通过使
用如下命令,用户可以确切地说明符号常数存放在什么位置:
LTORG

LTORG指示汇编程序在程序当前位置存贮所有已生成但未作为先前LTORG结果的符号
常数。在程序中,LTORG通常放在户定义的常量之前和程序最后一条指令之后。我们要求
读者在以后的程序中使用文字和ORG命令。
程序4-10解释字符符号常数及整数符号常数的使用。

*******************************************************************************************
* 本程序读若干输入纪录,每纪录上有两个数据,对每个纪录,程序打印这
* 两个数以及其平均数,程序逻辑为:
* 步骤1:打印一个标题
* 步骤2:试读第一个纪录
* 步骤3:如果文件结束、转步骤7
* 步骤4:从纪录上读两个数,计算其平均数
* 步骤5:打印这两个数及其平均数
* 步骤6:读下一纪录并返至步骤3
* 步骤7:打印“END OF REPORT”并退出
*********************************************************************************************
*
AVERAGE CSECT
USING AVERAGE,R15
*
***<步骤1> 打印一个标题
*
XPRNT =C'1AVERAGES REPORT",16
*
***<步骤2> 读入第一个纪录
*
XREAD CARD,80
*
***<步骤3> 循环直到数据读完
*
BC B'0100',EXIT
*
***<步骤4>
*
XDECI R2,CARD 得到第一个数
XDECI R3,0(R1) 和第二和数

- 65 -
*
LR R5,R3
AR R5,R2 R5存放两数和
*
M R4,=F'1' 被除数符号扩展至R4
*
D R4,=F'2' R5中得到了商(即平均值)
*
***<步骤5> 打印两个数及其平均值
*
XDECO R2,NUMBER1 转换第一个数到打印行
XDECO R3,NUMBER2 转换第二个数到打印行
XDECO R5,AVG 转换平均值到打印行
*
XPRNT PRNTLINE,93 打印结果
*
***<步骤6> 读下一个纪录,并转回步骤3
*
XREAD CARD,80
B LOOP
*
***<步骤7> 打印结束信息并退出
*
EXIT XPRNT =C'OEND of REPORT',14
BR R14 退出
*
LTORG
*
CARD DS CL80 输入数据空间
*
PRNTLINE DC C' 0 THE FIRST NUMBER WAS'
NUMBER1 DS CL12 存放第一个数
DC C' THE SECOND NUMBER WAS'
NUMBER2 DS CL12 存放第二个数
DC C' AVERAGE='
AVG DS CL12 存放平均值
*
R0 EQU 0
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6

- 66 -
R7 EQU 7
R8 EQU 8
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END AVERAGE

程序4-10

第十二节 取地址指令的使用

取地址指令LA是本章讨论的最后一条指令,这是一条完成简单的操作但却是非常有用
的指令,该指令的格式为::
LA r,D(X,B) (装入地址)
功能(r)← D+(X)+(B)

执行LA指令将分析D(X,B)的有效地址并将之送到r指定的寄存器中,本指令的执行不影
响条件码CC,指令的操作码为41。
例如,如下指令的执行:
LA R1,WORD
将把WORD的地址送入R1。
请仔细注意如下的两条语句间的不同:
LA R1,WORD
以及
L R1,WORD
第一条指令LA指令将R1指向WORD(即将WORD的地址送入R1),而第二条指令实际上将
WORD地址所在处的内容装入R1。因此,指令:
L R1,WORD
具有如下两个指令的效果
LA R1,WORD
L R1,0(R1)
以下强调两种特殊但是非常普通的LA指令的用法。
⑴.一个 0≤n≤4095 的值 n 可以通过执行如下形式的指令装入寄存器 r。
LA r,n (立即数、无基址和变址寄存器)
例如,如下指令的执行:
LA R2,20
将内存地址解释为值20,并放入寄存器R2中(设置R为00000014),这通常是一个有
效的将小的正数常量以二进制形式装入寄存器的办法;

- 67 -
⑵.假定一个寄存器含有一个非负整数。为了加一个小于 4096 的增量 n 到 r,可以使
用如下形式的指令。
LA r,n(r)(使用变址寄存器,无基址寄存器)
例如,如下执令的执行
LA R14,32(R14)
将32加到R14并把结果放入R14,这通常是用小的正数增加寄存器值的最有效的方
法。
在以后的若干节中,许多例子将涉及数据表格的操作。在这样的程序中,LA指令经常
出现。程序4-11建立一个数据表格,然后XDUMP出完成的表格。仔细研究程序的逻辑以及
LA指令的使用。

********************************************************************************************
* 本程序建立一个全字整数表格,并XDUMP出所完成的表格,它从每个
* 输入纪录读入一个数,当遇到最后一个包含999999的数据后得知输入
* 结束。程序的逻辑为:
* 步骤1:初始化R3指向表的第1项,R4对表项计数初始化为0
* 步骤2:读第一个纪录
* 步骤3:检查数据是否结束,如果是则转到步骤6进行XDUMP出整个表
* 步骤4:将数据放入表中
* 步骤5:读下个纪录并返回到步骤2
* 步骤6:XDUMP出表的内容并退出。
*******************************************************************************************
*
TABUILD CSECT
USING TABUILD,R15
*
***<步骤1> 初始化数据计数器和表格指针
*
SR R4,R4 计数器置0
LA R3,TABLE 表格指针指向表头
*
***<步骤2> 读入第一个纪录
*
XREAD CARD,80 本程序假定有纪录可读
*
XDECI R2,CARD 并且数据有效
*
***<步骤3> 检查数据是否结束
*
TRAILCHKC R2,=F'999999' 检查数据末尾标记
BE ENDINPUT 若结束则跳转到打印处处理
*
***<步骤4> 将合法数据加入到表中
*

- 68 -
ST R2,0(R3) 根据R3指明的地址放入表中
LA R4,1(R4) 计数器加1
LA R3,4(R3) 调整R3指向下一个表项
*
***<步骤5> 读下一个纪录
*
XREAD CARD,80
XDECI R2,CARD 下一个数在R2中
*
B TRAILCHK
*
***<步骤6> XDUMP出整个表格
*
ENDINPUT XDUMP TABLE,200
XDUMP
*
* 第二个XDUMP用于显示寄存器R4的内容,其中存放这表各种有效数据
* 的个数信息。
*
BR R14 退出
*
LTORG
CARD DS CL80 数据输入空间
TABEL DS 50F 可以存放50个数据的表格空间
R0 EQU 0
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6
R7 EQU 7
R8 EQU 8
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END TABUILD

程序4-11

- 69 -
习题四

1、请将以下RR类指令编码成为机器指令形式:
a. AR 1,15 b. SR 2,10 c. AR 14,3
d. SR 0,0 e. AR 15,14
2、写出如下被编码指令的等价符号表示:
a. 1820 b. 1AFE
c. 1BCC d. 1834
3、假定R0包含001A2F0B、R1包含FFFFA21C、R6包含000019EF,在以下每条指令之后给出所
有三个寄存器的内容:
a. LR 6,0 b. SR 6,0 c. AR 1,6
d. SR 1,6 e. SR 1,1
4、请将以下RR指令编码为机器指令形式:
a. AR 0,12 b. SR 13,1 c. AR 15,13
d. SR 15,15 e. SR 10,9 f. LR 12,9
5、写出如下被编码指令等价的符号表示:
a. 1BBB b. 18C1
c. 1A0F d. 1834
6、假定R0包含00088804、R1包含FFFFFF00、R8包含00001874,请给出以下每条指令之后所
有三个寄存器的内容:
a. LR 0,8 b. SR 0,1 c. AR 8,1
d. SR 1,0 e. SR 0,0 f. LR 8,8
7、请编码如下RX指令:
a. L 15,0(3,2) b. L 15,0(,2) c. L 15,0(2)
d. ST 2,20(14,13) e. L 15,24 f. ST 6,4(8)
8、写出如下被编码指令等价的符号表示:
a. 5801 500C b. 5010 0004 c. 5811 801C
d. 5845 E004 e. 5023 800C
9、假定R2包含000ABC10、 R3包含0000000B、R4包含000C1F11,计算如下每条指令执行要
访问的内存区域的地址,并确定是否会产生一个错误。
a. L 0,16(2) b. ST 1,16(4)
c. ST 15,20(3,4) d. L 8,0(2,4)
10、请编码如下RX指令:
a. L 14,12(1,3) b. ST 1,12(11,12) c. L 13,0(15)
d. ST 12,12(13) e. L 1,14(,2) f. ST 0,128
11、写出如下被编码指令等价的符号表示:
a. 5811 C008 b. 5020 2020 c. 5000 D00C
d. 5823 E028 e. 58FF 1000 f. 50E1 7008
12、假定R0包含0000FF0C、R1包含0000000D、R2包含000CCC08,计算将被如下指令访问的内
存区域的绝对地址,并确定是否会产生错误:
a. L 1,(4,2) b. ST 12,12(0)
c. ST 13,16(1,2) d. L 10,3(1,2)

- 70 -
13、输入ADD2的最新版本然后运行,仔细地检查汇编清单和XDUMP产生的打印输出。去掉BCR
指令,该指令从程序中退出。重新运行程序并注意:在执行完XDUMP后,就会试图执行
由WORD1表示的指令。因为WORD1的首字节为0,将产生一个错误(0不是合法的操作码)。
这样产生一个DUMP,DUMP给出了错误发生时的存贮映象。由于DUMP将在你作为程序员的
经历中再次出现,因此你应该逐渐熟悉DUMP的内容。
14、写一个包含两个常量的程序,一个等于10另一个等于20。程序将计算10-20并XDUMP出
答案。运行程序并验证答案。
15、写一个类似于程序4-3的程序,用户将任何希望的值放入W1,W2和W3中。程序找出3个全
字中最小的值,并利用XDUMP指令显示结果。
16、如果一个前7个字符包含**122A3被读入称为INPUT区域的数据记录读入后,INPUT的前7
个字节将是什么?
17、使用附录,确定如下字符在内存中的值:
$ @ # X 以及Z
18、如下程序读一些记录。在每读入一个记录后,包含记录映像的区域被XDUMP出;当产生
EOF时就退出。注解、敲入并运行该程序。使用两个或更多的记录作为输入,并对每个
记录输入一些字符。仔细地研究XDUMP结果验证映像就是你所期待的。
READ CSECT
USING READ,R15
LOOP XPEAD IN,80
BCR B'0100',14
XDUMP IN,80
BC B'1111', LOOP
IN DS CL80
END READ
19、写一个程序对一些数求和。程序要读入输入记录并从每个记录取一个数,当遇到文件结
束时,程序将XDUMP出答案并终止。
20、写出如下DC语句产生什么(使用十六进制表示产生的域的内容)。
(1) F1 DC CL5'A'
DC 2C'A'
DC 3CL2'B'
DC C'1'''
F49 DC C'&&0'
DC CL4'$$WORD'
(2) FIRST DC CL4'ABC'
DC 2C'1A'
DC 3CL5'&&$$'
LAST DC CL3'HELLO,WORLD'
21、写一个程序读入一些数(每记录1个数)并打印这些数的和。应在页顶部打印一个标题,
每个数都要按1行1个数打印出来,结果应打印在最后一行。
22、写一个类似于前面练习描述的程序,唯一的不同就是第一个输入记录包含紧随其后的记
录的个数。例如,如果如下数为输入则答案为7。
4 输入记录的个数
2 数据记录
-4 数据记录

- 71 -
8 数据记录
1 数据记录
23、写一个程序计算任意个数数据的交替和/差,其中数据的结束由一个小于0的输入标识。
不要在计算中包含小于0的数。例如,如果输入数据为
26 数据记录
43 数据记录
71 数据记录
122 数据记录
-6 步哨(结束)
答案将是26-43+71-122=-68
24、写一个程序求若干卡车的总重量。程序应读入任意个数的记录,每个记录包含一辆卡车
的重量。作为对读入的检查,记录应以一个合适的标签输出显示(即XPRNT输出)总重量。
25、假定:
R0 包含F01821F0
R1 包含FFFFFFFF
R2 包含00000003
R3 包含00000004
请给出如下指令执行后,寄存器的内容是什么。
a. MR 2,1 b. MR 0,2 c. MR 2,3 d. MR 2,2
进一步假定WORD1和WORD2定义如下:
WORD1 DC F'10'
WORD2 DC F'-2'
请给出如下指令执行后,寄存器的内容是什么。
e. M 0,WORD1 f. M 0,WORD2
g. M 2,WORD1 h. M 2,WORD2
26、假定:
R0 包含 F0F1F2F3F4
R1 包含 FFFFFFFE
R4 包含 00000002
R5 包含00000005
请给出在如下指令执行后,寄存器中的内容为什么。
a. MR 4,1 b. MR 0,4 c. MR 4,1 d. MR 4,4
进一步假定WORD3和WORD4定义为:
WORD3 DC F'9'
WORD4 DC F'-3'
请给出下如指令执行后,寄存器的内容是什么?
e. M 0,WORD3 f. M 0,WORD4
g. M 4,WORD3 h. M 4,WORD4
27、假定:
R1包含00000003
R2包含00000000
R3包含00000014
R4包含FFFFFFFF
R5包含FFFFFF10

- 72 -
请给出如下指令执行后,寄存器的内容是什么?
a. DR 2,1 b. DR 2,4 c. DR 3,5 d. DR 4,1
进一步假定WORD1和WORD2定义如下
WORD1 DC F'-4'
WORD2 DC F'14'
请给出如下每条语句的执行结果:
e. D 2,WORD1 f. D 2,WORD2
g. D 4,WORD1 h. D 4,WROD2
28、假定:
R1包含00000005
R2包含00000000
R3包含00000016
R4包含FFFFFFFF
R5包含FFFFFFEC
请给出如下每条执行后寄存器的内容是什么?
a. DR 2,1 b. DR 2,4 c. DR 2,5 d. DR 4,1
进一步假定WORD3和WORD4定义如下:
WORD3 DC F'-3'
WORD4 DC F'15'
请给出如下每条指令的执行结果:
e. D 2,WORD3 g. D 4,WORD3
f. D 2,WORD h. D 4,WORD4
29、在程序中定位错误称为调试(debugging),你会发现在ASSIST/I通过单步执行一条指令
很容易调试。然而,学习如何从程序中卸出中找出”发生了什么“仍是一个很有用的技
巧,当你在通常的IBM操作系统下运行程序时,当检测到一个错误,你必须仅从以下:
a. PSW
b. 寄存器
c. 存贮器的内容
确定出现的错误。前两个练习要求你仅仅从上述三种信息中,“确定你所能确定的出了
何种错”。如下DUMP你能确定什么?

PSW AT BREAK: FF000000 AF000018

R0-7 F4F4F4F4 00000029 00000083 00000000 F4F4F4F4 00000004


F4F4F4F4 F4F4F4F4
R8-15 F4F4F4F4 F4F4F4F4 F4F4F4F4 F4F4F4F4 F4F4F4F4 00000084
000000CC 00000000

USER STORAGE: CORE ADDESSES SPECIPIED- 000000 to 000A8C

000000 4720F078 1B33E00 F0280050 4740F026 *..0\.0..&. 0.*


000010 5350F028 50520000 5A20F080 E000F028 *.&0.&…!.0.\.0.*
000020 005047F0 F00C07FE F4404040 40404040 *.&.00…4 *
000030 40404040 40404040 40404040 40404040 * *

- 73 -
LINES:000030-000060 ARE IDENTICAL
000070 40404040 40404040 00000001 00000002 * ………*
000080 00000003 F5F5F5F5 F5F5F5F5 F5F5F5F5 *….555555555*
000090 F5F5F5F5 F5F5F5F5 F5F5F5F5 F5F5F5F5 *….555555555*
30、从以下 DUMP 中你能发现什么错误?

PSW AT BREAK: FF000000 9F00034

R0-7 F4F4F4F4 00000090 0000009B 00000000 F4F4F4F4 00000003


F4F4F4F4 F4F4F4F4
R8-15 F4F4F4F4 F4F4F4F4 F4F4F4F4 F4F4F4F4 F4F4F4F4 0000009C
000000E4 00000000

USER STORAGE: CORE ADDESSES SPECIPIED- 000000 to 000A8C

000000 4720F090 1B33E000 F03E0050 4740F026 *..0…\.0..&. 0.*


000010 5350F03E 50520000 5A20F098 E000F03E *.&0.&…!.0q\.0.*
000020 005047F0 F00C4110 F0901912 4780F03C *.&.00…0…..0.*
000030 5A300001 41110004 47F0F02A 07FEF340 *!……..00…3*
000040 40404040 40404040 40404040 40404040 * *
LINES:000040-000070 ARE IDENTICAL
000080 40404040 40404040 40404040 4040F5F5 * 55*
000090 00000001 00000002 00000003 F5F5F5F5 *…………..5555*
0000A0 F5F5F5F5 F5F5F5F5 F5F5F5F5 F5F5F5F5 *555555555555*
31、编写程序计算土地每平方英尺的价格。程序接收数据输入,每个数据记录中有三个数据:
土地长、宽和价格,程序打印这三个值,并打印计算结果。例如有如下输入:
250 75 2678
100 50 6754
程序应生成类似如下的报告:
LENGTH WIDTH COST IN COST/SQUARE FOOT
DOLLARS IN CENTS
250 75 2678 14
100 50 6754 135
32、编写程序计算客户支票账户的收支情况。程序将接受如下数据输入:
a.第一个输入记录中有一个数据,该数据表示初始余额;
b.其后的记录每个有 2 个数据。其中的第一个数据为 0 或 1,表示是存款还是提款。
程序在读入每个记录之后打印每个记录,以及计算出的当前余额。当所有数据处理完毕
后,程序打印:
a.提款的次数;
b.存款的次数;
c.提款的平均额;
d.存款的平均额
例如,以下的数据输入:
4295

- 74 -
0 5210
1 1011
1 1246
0 1000
则程序打印如下的内容:
CHECK/DEPOSIT AMOUNT BALANCE
CODE (IN CENTS) (IN CENTS)
4295
0 5210 9505
1 1011 8494
1 1246 7248
0 1000 8248
#CHECKS = 2
#DEPOSITS = 2
AVG.AMT./CHECK = 1128
AVG.AMT./DEPOSIT = 3105
33、编写程序统计考试成绩。输入数据包含一系列记录,每个记录中有一个分数(从 0 到
100 分)。程序打印类似下面的报表:
TEST SCORE SUMMARY
RANGE #SCORES % OF TOTAL
91-100 13 18
81-90 24 34
71-80 16 22
61-70 10 14
0-60 7 10
TOTAL # OF TEST SCORES = 70
34、假定一个商人投资了一定数量的资金,投资的利率是固定的。编写程序计算该笔资金 n
年后的投资收益(假定每年都要进行计算)。程序接受一个输入记录,该记录中有三
个数据,一个数据是初始投资额,一个是利率,一个是投资年限。程序产生类似如下
的打印输出结果。
INVESTMENT SUMMARY
ORIGINAL AMOUNT = 50000(IN CENTS)
PER CENT = 7
# YEARS = 4
# YEARS AMOUNT
1 53500
2 57245
3 61252
4 65540
AVERAGE INTEREST EARNED PER YEAR = 3884
35、编写一程序计算游泳池的容量和填满游泳池 80%用量需要的水量。程序接收数据输入,
每个记录包含三个数据:游泳池的长、宽、深,程序产生类似以下的打印输出:
CAPACITY REPORT
LENGTH WIDTH DEPTH VOLUME 80% FILLED

- 75 -
20 60 6 7200 5760
15 25 4 1500 1200
60 40 8 19200 15360
36、一家商店请你研究顾客的购买情况。你需要读入一组数据,每个记录包含两个值:所购
买商品的数量以及单价。你的程序必须得出以下的结果:
a.一次购买中同一商品的总价;
b.一次购买中商品的种类数;
c.一次购买中所有商品的平均价格
请设计一个输出报告。你的输出报告应该为每个记录打印 1 行(包含数量,单价,总价),
最后应该有总结行(含有 b、c 中所示的两个平均统计值)。所有输出应该有明显的提
示。

- 76 -
第五章 循环程序及内部子程序设计

第一节 循环程序设计

循环的构造是一个汇编程序员的最重要的任务之一。在上一章中,我们实际上已经使用
了普通的指令构造了循环程序,本章中我们将进行详细的分析并介绍专用的循环指令。
循环控制结构也是一种常见的控制结构,它有顺序、分支机构符合而成。这就是说,某
些分支要反复执行多次的这样一种结构,称为循环结构。而顺序分支结构在一次计算处理中,
最多只执行一次,在两个分支中只有一分支执行一次,而另一分支则不执行。利用循环的方
法设计算法或编写程序,可以简化程序设计,减少程序的书写量。即以少量的指令语句,完
成大量的操作,以节省存储空间。在循环控制结构中,可能又包含新的循环结构,这就出现
了循环中套循环的现象。如果一个循环结构仅包含顺序和分支结构,则称该循环为单重循环;
如果在单重循环结构内又包含了单重循环,则称该结构为二重循环。以此类推。二重循环以
上即称为多重循环。
以下我们讨论单重循环结构,多重循环结构类似,只不过更复杂一些。循环控制结构一
般有图5-1所示的两种形式:

F
S
B
T

S F
B
T

(a) (b)

图5-1 循环结构示意图

我们把图中的虚框部分作为循环控制结构的基本部分。对循环控制还可细分为如图5-2
所示的形式(相对于图5-1(b)的情形)。这样,循环控制便是由上述四个部分组成的。其
中的工作部分、修改部分及控制部分属于要反复执行的部分。现对上述四个部分做一些说明:
⑴.循环初始部分,又称恢复部分。这一部分是循环程序在开始执行时所要做的一些必
要(即第一次进入循环时)的工作,它保证循环体在正确的初始状态下开始工作。逻辑上,
现从这部分开始执行。
⑵.循环的工作部分。它是循环程序的主体,也称为循环体。这是要重复执行的部分。

- 77 -
循环初始化
(或恢复部分)

工作部分

修改部分

控制部分

图5-2 循环构成图

⑶.循环程序的修改部分。它保证循环体在循环的过程中,将按一定的规律改变循环控
制参数。
⑷.循环程序的控制部分。它保证按循环此书(或循环控制条件)结束循环操作,转出
循环体。
对于图5-1中所示的两种基本控制结构,我们把图5-1(a)称为先判断后循环结构,把
图5-1(b)称为先循环后判断结构。在本书的工作语言中,它们可以用以下两个语句来描述:
(a)while B do S
(b)repeat S until B
其含义是:对于(a),当B成立时进行S部分的处理,一直重复到条件B不成立时才转到后继
部分去处理;对于(b),重复进行S部分的处理,直到条件B成立时,终止S部分的处理,转
到后继部分去处理。
这两种循环结构的区别在于(a)可能一次都不执行而(b)至少要执行一次。另外,如
果同一个循环可以写成这两种结构形式的话,它们的条件B的写法应该刚好相反。
编程中我们总能遇到一种普遍的循环类型,那就是重复执行次数已知的类型,比如说n
次。这种循环通常形式如下:
步骤1:设I=n
步骤2:执行循环体
步骤3:设置I=I-1
步骤4:如I≠0,转步骤2
步骤5:继续其余的程序

算法5-1 循环次数已知的循环算法

循环效率对许多程序的性能至关重要,因此导致产生了专为建立循环机制而设计的指
令。接下来,我们将介绍两个专用的循环指令BCT和BCTR。

1. 计数循环指令BCT
计数循环指令BCT指令的格式为:
BCT r,D(X,B) (计数循环)

- 78 -
指令的执行将r的内容减1。如果r此时不包含0,则转移到由D(X,B)指定的地址去执行(即
继续循环)。如果r包含0,则执行BCT指令的下一条指令(即退出循环)。本指令的操作码为
46,指令的执行不影响条件码CC。

2. 计数循环寄存器指令BCTR
计数循环寄存器指令BCTR的格式为:
BCTR r1,r2 (计数循环寄存器)

除转移地址包含在r2中以外,这条指令完成和BCT同样的任务。本指令的操作码为06,
指令的执行不影响条件码CC。
然而,如果r2为R0,则只从r1中减1而不进行转移。
此时要弄清的是算法5-1给出的循环形式如何实施。假定,寄存器r没有在循环体中使用,
循环要执行n次,则可以用如下代码:
LA r, n
Label 循环体
……
……
BCT r, label
此外还可以使用下面的代码:
LA r1, n
LA r2, label
label循环体
……
……
BCTR r1, r2 (r2≠R0)
实际上,BCTR用于循环远少于BCT,其主要用途是将一个寄存器减1。例如,执行如
下指令:
BCTR R11,0
将从R11减1。因第二个操作数为0(即R0),不转移。这一循环的彻底讨论可以通过某些
例子说明得更加具体。
例1,经常要使程序重新利用打印行。在这种情形下,在再次填充这一行前通常用空格
覆盖该行。尽管一个清空打印行的更有效的方法将在以后讨论,到目前为止如下简单的循环
就可以达到这个目的:
IC R1,=C' '
LA R3,133
BLANK1 STC R1,PLINE-1(R3)
BCT R 3,BLANK1
……
PLINE DS CL133
关于该循环请注意:
⑴.IC R1,=C' '使用一个字符常量将R1最右字节设置为空格。

- 79 -
⑵.PLINE-1是一个使用变址寄存器的隐式地址。第一次通过循环体时R3将包含133,
PLINE-1(R3)将等于PLINE+132即指向打印行的最后一个字符。当R3包含1时最后一次通过
循环体,PLINE-1(R3)将等于PLINE。因此,整个打印行将被循环全置为空格。
例2,一程序读入5个数(每行一个)并求出它们的和,要写这样的程序,BCT可以用于这
一目的,如程序5-1所示。

******************************************************************************************
* 本程序累加5个输入数据的总和并打印结果,程序逻辑如下:
* 步骤1:累计值清零。循环计数器置为5;
* 步骤2:读入数据,累加;
* 步骤3:减循环计数器,如果大于0,转到步骤2;
* 步骤4:打印累总和;
* 步骤5:退出。
*******************************************************************************************
*
SUM5 CSECT
USING SUM5,R15
*
***<步骤1> 初始化累总值和循环计数器
*
SR R4,R4 累总值清0
LA R2,5 循环计数器置初值5
*
***<步骤2> 读入数据并累加
*
READNXT XREAD CARD,80
XDECI R3,CARD 得到数据
AR R4,R3 累加
*
***<步骤3> 检测循环是否结束
*
BCT R2,READNXT 若没有结束则跳转
*
***<步骤4> 打印结果
*
XDECO R4,TOTAL
XPRNT CRG,13 打印结果
*
***<步骤4> 退出
*
BR R14
*
********************************************************************************************
*

- 80 -
CARD DS CL80 输入数据空间
*
CRG DC C'1' 打印控制字符—换页
TOTAL DS CL12 总和存放处
*
LTORG
*
R0 EQU 0
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6
R7 EQU 7
R8 EQU 8
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END SUM5

程序5-1

例3,程序5-2从一个输入记录读入两个正数x和y,如果x<y则打印x/y的值,保留50位十
进制的小数。关于此程序请注意以下两点:
⑴.如果x或y不是正整数或者x不小于y,转到ERROR。
⑵.在指令DR R2,R4执行后,R3中包含的要打印的数字是二进制的。加上十进制数
240(即十六进制的F0)可以将二进制数字转换为一个EBCDIC字符(将会把00转换为F0,01
转换为F1,依次类推)。记住如果尚未转换为字符格式,就不能打印出二进制数对应的十进
制数字。

*******************************************************************************************
* 本程序读入两个正数,X和Y。然后计算X/Y的值,保留到小数点后的50位。
* 最后打印结果。假定X<Y。程序逻辑如下所示:
* 步骤1:读入数据X和Y。若读不到数据或数据非法,转步骤2,否则
* 转步骤3;
* 步骤2:打印出错信息后转步骤7退出程序;
* 步骤3:设置循环计数器初值为50。然后将指针指向输出行中第一个
* 字符的位置;
* 步骤4:计算当前位,然后放入打印行;

- 81 -
* 步骤5:指针加1以指向下一个字符位置,循环计数器减1,若不为0,
* 转回步骤4;
* 步骤6:打印结果;
* 步骤7:退出
*******************************************************************************************
*
EXPAND CSECT
USING EXPAND,R15
*
***<步骤1> 读入数据,若正常,转向步骤3
*
XREAD CARD,80
BC B’0100’,ERROR EOF也是错误
*
XDECI R3,CARD 得到X
BNP ERROR X必须为正
*
XDECI R4,0(R1) 得到Y
BNP ERROR Y必须为正
*
CR R3,R4 X必须小于Y
BL OK 若所有数据均正常转向OK
*
***<步骤2> 打印出错信息并退出
*
ERROR XPRNT =C'1ERROR IN INPUT',15
B EXIT
*
***<步骤3> 设置循环计数器初值和指针初值
*
OK LA R5,50 置循环计数器初值为50
LA R6,EXOUT 置指针初值指向第一输出位
*
***<步骤4> 计算数位
*
LOOP M R2,=F'10' 被除数乘10
DR R2,R4 要打印的数位为除法的商
LA R3,240(R3) 转换为字符形式
STC R3,0(R6) 存入打印行
LR R3,R2 余数作为下一次的被除数
*
***<步骤5> 调整循环计数器和指针值
*
LA R6,1(R6) 指针加1指向下一位

- 82 -
BCT R5,LOOP
*
***<步骤6> 打印结果
*
XPRNT CRG,52
*
***<步骤7> 退出
*
EXIT BR R14
*******************************************************************************************
*
CARD DS CL80 输入数据空间
*
CRG DC C'1' 打印控制字符
EXOUT DS CL50 结果存放空间
*
LTORG
*
R0 EQU 0
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6
R7 EQU 7
R8 EQU 8
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END EXPAND

程序5-2

除了循环次数已知的循环以外,在编程中我们还会经常遇到直到循环的初始值、步长值
和终值的循环。在本书的工作语言中,它可以用以下语句来描述:
(a)for(i=初始值;i<=终值;step=n)do S
(b)for(i=初始值;i>=终值;step=-n)do S
用到目前为止介绍的全部指令当然可以实现这样的循环。然而,这样的逻辑出现得如此
频繁,以致要介绍以下两个特别的指令BXLE和BXH。

- 83 -
3. 小于等于循环指令BXLE
小于等于循环指令BXLE的格式为:
BXLE r1,r2,D(B) (小于等于循环)

要描述BXLE指令执行产生的动作,我们将涉及索引寄存器,增量寄存器以及界限寄存
器。这些寄存器定义如下:
⑴.r1 为索引寄存器
⑵.如r2为偶数寄存器,它表示一个偶/奇寄存器对。在这种情况下,偶数寄存器为增
量寄存器,奇数寄存器为界限寄存器。
⑶.如r2为奇数寄存器,则r2同时为增量寄存器和界限寄存器。
指令BXLE执行后产生的动作为:
⑴.增量寄存器的内容加到索引寄存器中;
⑵.如索引寄存器的新内容小于或等于界限寄存器,产生往D(B)的转移(即继续循环)。
否则,执行紧接着BXLE指令的下一条指令(即退出循环)。
BXLE指令的操作码为87。
敏感的读者会注意到BXLE的格式同通常的RX格式不一致。事实上,BXLE是RS类的指
令。RS指令的符号形式为:
标号 助记符 r1,r2,D(B)
其中r1和r2是寄存器编号,D(B)是一个地址(无变址寄存器)。它所产生的机器指令的格
式为:

hOhOhr1hr2 hBhDhDhD
其中:hOhO 为操作码
hr1 为寄存器r1
hr2 为寄存器r2
hB 为基址寄存器
hDhDhD 为12位偏移
RX类指令与RS指令有两个主要不同:
⑴.RS指令有三个操作数而RX只有二个
⑵.RX必须用变址寄存器而RS不能
例:假定存在一个全字表TABLE,且R3包含表最后一项的地址。如下两段代码每段都
扫描TABLE以找出表中最小整数。在第一种情况下,没有采用BXLE,在第二种中采用了
BXLE。

* 假定表中至少有两个表项
* R3存放表格中最后一项的地址
L R4,TABLE R4=最小整数
LA R5,TABLE+4 R5=待查表项地址
LOOP C R4,0(R5) 比较取出的数和当前最小数大小
BNH CHKEND 若大于等于则跳转
L R4,0(R5) 否则置为最小整数
CHKEND LA R5,4(R5) 指向下一表项
CR R5,R3 到达表格最后吗?

- 84 -
BNH LOOP 未到继续循环

* 本段代码使用BXLE指令
LA R2,4 R2=表项长度
L R4,TABLE R4=最小整数
LA R5,TABLE+4 R5=待查表项地址
LOOP C R4,0(R5) 比较取出的数和当前最小数的大小
BNH CHKEND 过大于等于则跳转
L R4,0(R5) 否则置为最小整数
CHKEND BXLE R5,R2,LOOP 未到表格最后则继续循环

请比较以上两段代码。我们可以看到 BXLE 的使用显著地减短了循环体的指令序列。


使用 BXLE 后程序的执行时间显著减少。事实上,在要搜索表的大部分程序中,我们都应
该使用 BXLE 指令。

4. 大于循环指令BXH
大于循环指令BXH的格式为:
BXH r1,r2,D(B) (大于循环)

本指令是另一个同BXLE指令密切相关的RS指令。述BXH指令时涉及的索引寄存器,增
量寄存器以及界限寄存器与BXLE指令中的相同。
指令BXH执行后产生的动作为:
⑴.增量寄存器的内容加到索引寄存器中;
⑵.如索引寄存器的新内容大于界限寄存器,产生往D(B)的转移(即继续循环)。否则,
执行紧接着BXH指令的下一条指令(即退出循环) 。
BXH指令的操作码为86。
使用BXH指令的关键在于将一个负值送入增量寄存器。因此,我们先前的扫描表以找
出最小整数的例子,可以编码如下:

* 假定表中至少有两个表项
* R3存放表格中最后一项的地址
L R4,=F'-4' R4中存放增量-4
LR R6,R3 R6中存放最后一个表项的地址
L R2,TABLE R2中存放目前为止最小整数
LA R5,TABLE R5中存放表格首址(界限)
LOOP C R2,0(R6) 比较
BNH CHKEND
L R2,0(R6) 置为新的最小整数
CHKEND BXH R6,R4,LOOP

在上述例子中,再次假定R3指向表的最后一项。
读者此时可能奇怪,为什么程序会选择在BXLE和BXH指令的第二操作数上使用偶数寄
存器。只要稍微聪明一点,通过使用增量寄存器同时作为界限寄存器,使寄存器个数减少一

- 85 -
个来实现循环是可能的。例如,上面给出的一段代码不必使用寄存器R4。作为替代,可以
使用如下代码:

L R5,=F'-4' 增量和界限寄存器都是R5,初值为-4
LR R6,R3 R6中存放表格中最后一项的地址
LA R2,TABLE+4 现在将R6从指针转换位偏移量
SR R6,R2
L R2,TABLE R2中存放到目前为止最小整数
LOOP C R2,TABLE+4(R6) 检查此值是否更小
BNH CHKEND
L R2,TABLE+4(R6)
CHKEND BXH R6,R5,LOOP 继续循环

可能会存在这样一个问题,如何判断何时使用BXLE或BXH。在每次将固定增量加到指
针的循环中,BXLE和BXH都可以被使用。在进入循环之前,将用几条语句初始化循环变量,
但是如果程序执行循环不止一次或二次的话,执行时间方面的好处远比付出的多。
另一个经常出现的问题是:为什么要关心在这儿和那儿节省几条指令?的确,花在如何
节省程序执行时间上的时间可能太多。许多程序执行时间的主要部分主要花在循环处理上。
因而,努力优化这些循环中的代码非常重要。循环编码的有效可能会加速程序执行达100%
或更多。在此,花小部分时间学习如何有效地编写循环代码将使未来的任务非常容易。

第二节 内部子程序设计

人们在编写程序时,常常会遇到这样的情况:某些几乎相同的指令组(完成相同功能,
或只是加工的数据略有不同)需要在一个程序的若干不同的地方使用多次。而这些指令组所
包含的指令,可能少则几条十几条,多则数十条几百条。如果每一处都把它们重复写一次,
这显然太浪费程序设计时间和计算机的存储空间,但是这又不能设计为循环程序。因此,人
们通过实践,在程序设计的早期就提出了一种较好的方法,这就是把这组指令分离出来,单
独写成一个所谓的“子程序”并建立进入它和从它出来时所需要的连接信息。然后只需在需
要处调用以下这个子程序即可,换言之,子程序方法使得人们把“多次编写”的情况变成了
“一次编写,多次调用”。
在复杂程序的设计中,我们可以将程序分成更小的子程序。每个子程序的逻辑远比整个
程序的逻辑简单。这样我们可以集中精力考虑程序的构造,然后再各个突破,从而使得整个
程序开发的逻辑性大大增强,程序的维护性也到提高。
为了实现这一技术,有益的是首先画方块图,该图表示给定程序的分段。然后构造每个
独立的子程序的流程图以完成程序的逻辑描述。有时,子程序自身复杂得足够再画方块图。
在这种情形下,通过将子程序打破为“更低级”的子程序逐步完成。
作为这一过程的解释,考虑创建一个完成如下任务的程序:
1.将一系列以升序排序的数读入一张表;
2.打印出表中的数
3.读入第二张表;
4.打印第二张表;

- 86 -
5.合并两张表。
6.打印出这张大表
一个完成这6个任务而未利用子程序的详细流程图将大且复杂。如果代之以将程序分为
子程序,整个逻辑将如图5-3所示。

开始

步骤1
调用 READ 子程序读表 1

步骤2
调用 PRINT 子程序打印表 1

步骤3
调用 READ 子程序读表 2

步骤4
调用 PRINT 子程序打印表 2

步骤5
调用 MERGE 子程序合并
表 1 和表 2 到表 3 中

步骤6
调用 PRINT 子程序打印表 3

结束

图5-3 程序逻辑图

下面我们介绍调用子程序所需要的指令。然后我们再给出图5-3所对应的程序例子。

1.分支连接指令BAL
分支连接指令BAL的格式为:
BAL r,D(X,B) (分支连接)

这一指令的执行将引起如下动作发生:
⑴.程序状态字PSW的最右32位存贮在r中。这意味着在BAL执行后,r将存放紧接着BAL
指令的下一条指令的地址(因PSW中最右31位为下一条指令的地址)。
⑵.转移到D(X,B)执行
本指令的操作码为45,指令的执行不影响条件码CC。

- 87 -
例如,执行BAL R10,SORT将转移到SORT。更进一步地,调用完子程序后要返回主
程序。由于调用的时候BAL指令在r中保留了BAL语句之后的指令的地址,所以只要利用BR
r指令即可实现从子程序的返回。因此,对于刚才的例子,使用BR R10可实现返回。
程序5-3使用BAL指令实现图5-3描述的逻辑。请仔细阅读程序,学会内部子程序编写和
调用的方式。现在请不要花时间在子程序的细节上,而应注意子程序调用和返回的方式,该
本章全部结束后,请重新阅读本程序的细节。

********************************************************************************************
* 本程序展示子程序的使用。程序完成以下功能:
* 1)读入一组数据到表TABLE1中,假定输入数据已经按升序排好序;
* 2)打印表TABLE1中所有数据;
* 3)读入一组数据到表TABLE2中,假定输入数据已经按升序排好序;
* 4)打印表TABLE2中所有数据;
* 5)合并表TABLE1和表TABLE2到表TABLE3中;
* 6)打印表TABLE3中所有数据;
* 程序的逻辑如下所示:
* 步骤1:调用READ子程序读入第一张表,需要传递给子程序的参数是
* 表格的地址。READ子程序将返回填好的表格的最后一项的地
* 址,此地址放在变量ENDT1中;
* 步骤2:调用PRINT子程序打印TABLE1的内容;
* 步骤3:调用READ子程序读入第二张表,最后一项地址放在ENDT2中;
* 步骤4:调用PRINT子程序打印TABLE2的内容;
* 步骤5:调用MERGE子程序把第一二张表的内容合并到第三张表中;
* 步骤6:调用PRINT子程序打印TABLE3的内容;
* 步骤7:退出
********************************************************************************************
*
MAINPROG CSECT
USING MAIN,R15
*
***<步骤1> 读入第一张表
*
LA R2,TABLE1 R2用作子程序的入口参数传递
* TABLE1的地址
BAL R10,READ 调用READ
ST R3,END1 保存表中最后一项数据的地址
*
***<步骤2> 打印第一张表
*
BAL R10,PRINT R2中必须有表格的首址
* R3中必须有最后一项的地址
***<步骤3> 读入第二张表
*
LA R2,TABEL2 解释同第一张表

- 88 -
BAL R10,READ
ST R3,ENDT2
*
***<步骤4> 打印第二张表
*
BAL R10,PRINT 解释同第一张表
*
***<步骤5> 合并两张表
*
LA R2,TABEL1 R2中放第一张表的地址
LA R3,TABEL2 R3中放第二张表的地址
LA R4,TABEL3 R4中放第三张表的地址
*
L R5,ENDT1 R5中放第一张表中最后一项的地址
L R6,ENDT2 R6中放第二张表中最后一项的地址
*
BAL R10,MERGE 调用MERGE子程序
*
***<步骤6> 打印第三张表
*
LA R2,TABLE3
LR R3,R7 MERGE子程序把表3中的最后一项的
* 地址放在R7中
BAL R10,PRINT
*
***<步骤7> 退出
*
BR R14
*
********************************************************************************************
* 主程序的数据区域
********************************************************************************************
*
ENDT1 DS F 存放表格1中最后一项的地址
ENDT2 DS F 存放表格2中最后一项的地址
TABLE1 DS 50F 表格1
TABLE2 DS 50F 表格2
TABLE3 DS 100F 表格3
*
*
********************************************************************************************
* 子程序READ
* 进入本子程序时,R2应包含待填表格的首地址。每个输入记录包含一个数据。
* 本子程序把读到的数据填入表格中。当读到数据999999时数据输入结束。本

- 89 -
* 子程序退出时,R3中包含表格中最后一项数据的地址。本子程序没有改变其
* 它任何寄存器。应使用R10作为连接寄存器。
* 程序逻辑如下所示:
* 步骤1:读数据记录;
* 步骤2:若数据为999999,转向步骤5;
* 步骤3:把数据存入表格,调整表格指针;
* 步骤4:读下一个数据记录,转回步骤2;
* 步骤5:设置R3指向表格中最后一个数据的地址;
* 步骤6:退出
********************************************************************************************
*
READ STM R4,R2,READSAVE 保存除R3之外的所有寄存器
*
***<步骤1> 读入第一个数据
*
XREAD CARD,80
XDECI R4,CARD
*
***<步骤2> 测试是否数据结束
*
READLOOP C R4,=F’999999’
BE SETLAST
*
***<步骤3> 保存入表格中并调整表格指针
*
ST R4,0(R2)
LA R2,4(R2)
*
***<步骤4> 读下一个数据,转回步骤2
*
XREAD CARD,80
XDECI 4,CARD
B READLOOP
*
***<步骤5> 置R3指向表格中最后一个数据
*
SETLAST LR R3,R2
S R3,=F’4’
*
***<步骤6> 退出
*
LM R4,R2,READSAVE 恢复除R3之外的全部寄存器
BR R10
*

- 90 -
LTORG
*
CARD DS CL80
*
READSAVE DS 15F
*
*
********************************************************************************************
* 子程序PRINT
* 本子程序打印R2指向的表格。R3总包含表格中最后一项数据的地址。R10
* 是连接寄存器。每行打印一个数据。本子程序没有改变其它任何寄存器。
* 程序的逻辑如下:
* 步骤1:打印表头;
* 步骤2:如果所有数据打完,转向步骤5;
* 步骤3:打印一个表项,调整指针指向下一个表项,转向步骤2;
* 步骤4:打印“表格结束”信息;
* 步骤5:退出
********************************************************************************************
*
PRINT STM R0,R15,PRINTSAV 保存全部寄存器
*
***<步骤1> 打印表头
*
XPRNT =C’0*** START OF TABLE ***’,23
*
***<步骤2> 检查数据是否结束
*
PRINTLP CR R2,R3
BH PRINTEND
*
***<步骤3> 打印一行并转回步骤2
*
L R4,0(R2)
XDECO R4,OUTVAL
XPRNT PRNTLINE,13
*
LA R2,4(R2)
B PRINTLP
*
***<步骤4> 打印“表格结束”信息
*
PRINTEND XPRNT =C’0*** END OF TABLE ***’,21
*
***<步骤5> 退出

- 91 -
*
LM R0,R15,PRINTSAV 恢复所有寄存器
BR R10
*
LTORG
*
PRNTLINE DC C’ ‘
OUTVAL DS CL12
*
PRINTSAV DS 16F
*
********************************************************************************************
* 子程序MERGE
* 本子程序用于将两张表格合并为第三张表格,程序的入口参数如下规定:
* R2 = 第一张表的地址
* R3 = 第二张表的地址
* R4 = 第三张表的地址
* R5 = 第一张表中最后一项数据的地址
* R6 = 第二张表中最后一项数据的地址
* 程序处理完毕后,R7中将包含第三张表中最后一项数据的地址。R10作为
* 连接寄存器。本子程序只修改了R7的内容。
* 程序逻辑如下:
* 步骤1:如果所有表格1和表格2中的数均已插入到表格3中,转到步骤6;
* 步骤2:如果表格2处理完,或者表格1中还有数据并且从表格1中取出的
* 下一个数据小于从表格2中取出的下一个数据,则转步骤3,
* 否则,转步骤4;
* 步骤3:把值从表格1中移出到表格3中,调整表格1的指针,转步骤5;
* 步骤4:把值从表格2中移出到表格3中,调整表格2的指针;
* 步骤5:调整表格3的指针,转步骤1;
* 步骤6:置R7为表格3中最后一个数据的地址;
* 步骤7:退出
********************************************************************************************
*
MERGE STM R8,R6,MERGESAV 保存除R7外的所有寄存器
*
***<步骤1> 如果所有数据处理完毕,转步骤6
*
MERGELP CR R2,R5
BNH SOMELEFT
*
CR R3,R6
BH POINTR7
*

- 92 -
***<步骤2> 判断从哪一个表中取数
*
SOMELEFT CR R3,R6
BH FROMT1
*
CR R2,R5
BH FORMT2
*
L R8,0(R2)
C R8,0(R3)
BH FROMT2
*
***<步骤3> 从表格1中取数存入表格3
*
FORMT1 L R8,0(R2)
ST R8,0(R4)
LA R2,4(R2)
B INCRT3
*
***<步骤4> 从表格2中取数存入表格3
*
FORMT2 L R8,0(R3)
ST R8,0(R4)
LA R3,4(R3)
*
***<步骤5> 调整表格3的指针
*
INCRT3 LA R4,4(R4)
*
B MERGELP
*
***<步骤6> 置R7为表格3中最后一个数据的地址
*
POINTR7 LR R7,R4
S R7,=F’4’
*
***<步骤7> 退出
*
LM R8,R6,MERGESAV 恢复除R7外的所有寄存器
BR R10
*
MERGESAV DS 15F
LTORG
R0 EQU 0

- 93 -
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6
R7 EQU 7
R8 EQU 8
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END MAINPROG

程序 5-3

从这个程序示例中我么可以看出为什么要使用子程序:
⑴.节省了大量的代码,如READ仅编写一次,在程序中访问二次。
⑵.子程序使程序的逻辑结构更清晰。因此,尽管MERGE仅调用一次,它完成了逻辑
上有特色的任务,因而也编写为一个独立的子程序。
当定义子程序时,记住以下几点:
⑴.所有的输入习惯都应在注解语句中清楚说明,以便子程序能在程序的任何位置正确
地调用;
⑵.任何要改变的事情都应在注解中清楚地注明,除非要返回信息给调用程序,被改变
的寄存器在从子程序返回之前都应恢复至原先的内容。

2.分支连接寄存器指令BALR
分支连接寄存器指令BALR的格式为:
BALR r1,r2 (寄存器转移连接)

除了分支的地址取自于r2外,这一指令类似于BAL指令。本指令的操作码为05,指令的
执行不影响条件码CC。
例如,如下指令的执行:
LA R9,ROUTINE
BALR R10,R9
等价于如下指令的执行
BAL R10,ROUTINE
BALR的主要用途在于外部子程序(我们将在后面的章节中谈到),BALR有一个特别的
规则,即如r2为0,则不产生分支。这样的指令有什么用途呢?通常我们可以用它来指定基
址寄存器,这是一个重要的用法,请读者牢记。
例如:

- 94 -
BALR R12,0
USING BASEAD,R12
BASEAD ……
BALR将把BASEAD的地址送入R12,通过USING指令汇编程序知道用R12作为基址寄
存器。

调用子程序时通常需要进行现场的保护,从子程序返回时通常要进行现场的恢复,现场
主要包含返回指令地址、寄存器、内存区等,下面我们介绍两条用于保存和恢复现场的指令
STM和LM。

3.多寄存器保存指令STM
指令STM有效地用于一次保存几个寄存器的内容。这是一个RS类型的指令,它的格式
为:
STM r1,r2,D(B) (多寄存器保存)

指令的执行,将把寄存器r1到r2一次保存到开始于D(B)地址的相邻存贮空间。本指令的
操作码为90,指令的执行不影响条件码CC。
例如,如下指令:
STM R4,R8,SAVE
将把R4、R5、R6、R7和R8的内容存放到自SAVE开始的五个全字中。如果r2小于r1,则
存贮r1到R15后接着存贮R0到r2。例如,如下指令的执行:
STM R14,R1,SAVE
将把4个寄存器R14,R15,R0和R1的内容存放到SAVE。

4.多寄存器恢复指令LM
多寄存器恢复指令LM的格式为:
LM r1,r2,D(B) (多寄存器恢复)

除了寄存器的内容是装入而非保存外,LM其它方面同STM。它的操作码是98,指令的
执行不影响条件码CC。
例如指令:
LM R12,R2,REGSAVE
从REGSAVE开始的七个全字中取出内容,装入寄存器R12到R15以及R0到R2。在前面,
我们曾强调子程序应恢复执行期间被改变的寄存器的内容。用STM和LM能非常容易地实
施。例如,如下代码:
ROUTINE STM R0,R15,SAVE

子程序体

LM R0,R15,SAVE
BR R10
SAVE DS 16F
由于进行了寄存器的保存和恢复,所以子程序中允许在执行期间改变除了基址寄存器外
的任何寄存器,从而给程序设计者带来了极大的便利。

- 95 -
5.A型地址常量
由于调用子程序一定涉及到子程序的地址,所以在本节的最后我们讨论一下A型地址常
数。A型地址常数的定义形式为:
label DC A(exp)

在DC形式的指令中,操作数被看作是A-型地址常量。这条语句根据如下规则产生一
个全字:
⑴.如果表达式是一个非负整数,产生的全字将为整数的二进制表示(例F'exp'可以用于
代替A(exp)。
⑵.如果exp是一个label或label±n的形式,生成的全字将包含合适的地址。
因此,语句
DC A(5)
产生00000005,而
DC A(SAVE)
将产生一个包含SAVE地址的全字,一个DC语句中可以定义多个A型地址常数。例如,
语句
DC A(L1,20,L2)
将产生三个字:第一个包含L1的地址,第2个为值20,第3个为L2的地址。
A型常量也可以以符号常数的形式来使用,这常常用于调用子程序前装入输入参数到寄
存器中。例如,程序5-3的步骤5中,指令:
LA R2,TABLE1
LA R3,TABLE2
LA R4,TABLE3
L R5,ENDT1
L R6,ENDT2
可以被替换为:
LM R2,R4,=A(TABLE1,TABLE2,TABLE3)
LM R5,R6,ENDT1
当一个程序中同时处理几个大表时,A型符号常数特别有用。通常,指令
LA R1,TABLE

L R1,=A(TABLE)
具有相同的效果。主要区别在于,TABLE作为基址寄存器内容的偏移,要使用LA则
TABLE必须为可寻址的。因为最大偏移为4095,这意味着TABLE必须在相对于当前基址寄
存器地址的4095字节以内。另一方面,使用
L R1,=A(TABLE)
仅需符号常数自身的地址就可访问。作为一个例子,假定程序5-3中TABLE1和TABLE2
都包含500个全字而不是50个。在这样的情况下,使用:
LA R4,TABLE3
因TABLE3超过基址寄存器地址4096字节以外,将产生地址不可访问错误。而指令:
L R4,=A(TABLE3)
的使用将是可以接受的,请读者牢记这一点。

- 96 -
习题五

1、对如下每个循环指出循环体要执行的次数。
a. LA R14,6
LOOP :
BCT R12,LOOP
b. LOOP LA R12,200

BCT R12,LOOP
c. LA R10,LOOP
LA R11,143
LOOP :
BCTR R11,R10
d. LA R0,LOOP
LA R1,10
LOOP :
BCTR R1,R0
e. SR R1,R1
LOOP :
BCT R1,LOOP
f. LA L1,18
LOOP :
BCT R1,LOOP
g. LOOP LA R13,15

BCT R13,LOOP
h. LA R1,LOOP
LA R0,13
LOOP :
BCTR R0,R1
i. LA R0,LOOP
LA R2,15
LOOP :
BCTR R3,R0

2、对如下每个循环指出循环体要执行的次数。
a. LA R5,TABEND-10
L R6,=F'-10'
LA R7,TABLE-10
LOOP :
BXH R5,R6,LOOP

- 97 -
TABLE DS 8CL10
TABEND DS 0C
b. LA R3,TABLE+20
L R4,=F'-5'
LA R5,TABLE
LOOP :
BXH R3,R4,LOOP

TABLE DS 10CL5
c. LA R5,40
L R7,=F'-10'
LOOP LA R2,TABLE(R5)

BXH R5,R7,LOOP
d. LA R3,TABLE
LA R4,5
LA R5,TABLE+20
LOOP :
BXLE R3,R4,LOOP
e. L R7,=A(TABLE)
LA R8,4
L R9,=A(TEND)
LOOP :
BXLE : R7,R8,LOOP
TABLE DS 5F
TEND DS 0F
f. LA R7,TEND-4
L R8,=F'-4'
LA R9,TAB-4
LOOP :
BXH R7,R8,LOOP

TAB DS 100F
TEND DS 0F
g. LA R11,TABLE+160
L R14,=F'-8'
LA R15,TABLE
LOOP :
BXH R11,R14,R15
h. LA R7,100
L R11,=F'-8'
LA R15,TABLE
LOOP LA R10,TAB(r)

- 98 -
BXH R7,R11,LOOP

TAB DS 101CL10
i. LA R1,TABLE
LA R4,18
LA R5,TABLE+180
LOOP :
BXLE R1,R4,LOOP
j. L R1,=A(TAB)
LA R10,4
L R11,=A(TEND-4)
LOOP :
BXLE R1,R10,LOOP
TAB DS 50F
TEND DS 0F
3、写一个程序建立不多于50个全字整数的表。建完表后,程序应打印出表中的值,每行n
个,其中n为一个输入值,程序的输入由具有如下特征的一系列记录构成:
⑴.第一个记录包含n(将小于等于8),n为每个打印行要显示的整数的个数;
⑵.剩下记录每个记录包含一个要存入表中的整数。
在建立完表后,程序先XDUMP表的内容,然后每行打印n个,应注意考虑如下几点:
a. 在建立和打印表时,使用索引寄存器访问打印行中的全字或12字节域常方便些;
b. 要建一个表,应使用一个具有奇增量寄存器的包含BXH的循环;
c. 避免出现如下类型的指令序列
LA R3,4(R3)
CR R3,R5
BNH LOOP
要使用一条BXLE指令或BCT指令。
4、写一个程序完成如下操作:
a. 读入一系列记录,每个包含一个整数,按记录的值建立一个达50个全字的表,在往
表里存放记录时,程序应累记它们的和。
b. 程序计算表的平均值,XDUMP出表的内容,和及平均值。
c. 最后,程序应计算和打印表中大于平均值的值的个数、等于平均值的值的个数以及
小于平均值的值的个数。
5、假定银行为某天记录了每半小时银行开放期间客户的数量。进一步地假定开了总共7个小
时,因此共有14个时间区间记录了人数。写一个程序读入14张卡,其中每个卡包含在相
应半小时期间进入银行的人数,打印同如下格式一致的报表。
BANK ACTIVITY REPORT
PERIOD #OF CUSTOMERS %OF TOTAL CUMULATIVE%
1 8 2 2
2 13 4 7
3 12 4 11
4 18 6 17
5 24 8 25
6 23 8 33

- 99 -
7 37 12 46
8 33 11 57
9 29 9 67
10 18 6 73
11 19 6 80
12 17 5 85
13 21 7 93
14 20 6 100
TOATL #CUSTOMERS=292
6、假定一个公司维护一个包含细化到公司销售每项商品有多少件存贷信息记录的主文件。
尤其是,每个记录包含两个非负整数:一个商品项目代号(对公司销售的每个产品而言
是唯一的),以及存贷的件数。更进一步地,记录按项目代号升序排列。写一个程序处
理关于仓库中有多少存贷信息的请求。作为输入程序应接受:
⑴.主记录
⑵.包含-1的一条记录
⑶.一系列请求记录,每个包含一个项目代号
程序应首先读入主记录并选一张表,表中每项应包含两个全字,第一个全字包含项目代
号第二个包含该项产品的存贷件数。在读到包含-1的记录后,程序应DUMP出构造好的
表以便检验表的内容。然后,当读完请求记录后,应产生类似于如下形式的报告:
INVENTORY INFORMATION REPORT
ITEM # UNITS IN STOCK
234 842
421 0
24 NO ENTRY IN MASTER DECK
1840 13
7、写一个程序完成如下任务:
a. 首先程序读入一系列记录,每个记录包含两个整数,将其值存入两个独立的表中,
至多有50个输入记录,程序应当XDUMP出两个表的内容。
b. 通过将现存两个表中的相应表项相加,程序构造第三张表。因此,如果-5和24分别
为表一和表二的第4个元素,19将是要产生的存放于第三张表中的第4个元素。
8、写一个程序
(1)读入一个整数表并XDUMP出来;
(2)通过移掉重复的表项压缩表;
(3)打印产生的表。
每个输入记录包含一个整数,至多有30个输入值(假定值以升序出现)。压缩一个表的
办法如下:
a. 设置指向第一个表项的两个指针。第一个指针给出要查看的下一个表项,第二个指
针给出下一个要存放值的位置。
b. 移动第一个指针经过除了最后一条记录外的所有表项。当一个值不等于其后继的时
候,将其移动到第二指针指示的位置并更新第二个指针。
c. 最后,移动完最后一个表项。确信你的程序在少于两个值的情况下工作。在打印完
一个合适的表头后,每行打印一个值。
9、该程序在如下二个方面不同于练习前一个练习。
a. 值可能不按升序出现,(在开始编程之前先将你的算法仔细地想出来)。

- 100 -
b. 每个记录可包含任意个数的值。为了建立输入记录的值,定义CARD输入域如下:
CARDDS CL80
DC C'*'
然后重复使用XDECI直到条件码为3为止。
10、写一个程序完成如下任务:
a. 读入一系列记录,每个记录包含一个整数。将整数存放于一个全字表中。你可以假
定记录如此安排以便每个整数至少同其前一个一样大。
b. 打印表的内容。
c. 从表中去掉重复的元素。因此,如果三个连续的字每个都包含4,产生的表将只包含
一个值4。
d. 打印压缩后的表。
你的程序应利用三个子程序----一个读入整数表,一个打印整数表,另一个压缩整数表。
子程序应能用来读、写、打印任何表而并非局限于本程序的表,这意味着表的地址应
作为每个子程序的输入参数。
11、写一个程序完成以下任务:
a. 读入一系列记录,每个记录包含一个整数,将其值存放于一个全字表中,当一个包
含0的记录读入时确定输入系列的结束。0不进入表内。
b. 显示表的内容。
c. 读入一个要存放于第二个表的一系列记录,系列的结束再次以0表示。
d. 显示第二张表的内容。
e. 建立第三张表,其值仅为那些在先前两个表都出现的值。
f. 打印第三张表的内容。
你的程序应包含三个子程序:一个读入整数系列并构造表,一个打印表的元素值,一
个建立二个表都出现的整数的表。你可假定其中任何一张表都不包含两个相同的元素。
12、写一个程序完成如下任务:
a. 读入一系列记录,每个记录包含一个测验成绩,其范围为从0到100。这些成绩然后
存放在一个全字表中。
b. 显示表中的成绩。
c. 重新组织成绩的使它们按降序排列,这种处理通常称为对表排序。
d. 再次显示表的内容。
你的程序应利用三个内部子程序:一个读测验成绩,一个显示测试成绩,一个对成绩排
序。

- 101 -
第六章 综合程序设计

第一节 程序文档的标准

读者可能已经留意到前几章中的例子程序中我们采用了一定的程序文档标准,按照这样
的标准编写程序可能会有些繁,可是从软件开发和维护的角度而言,它们是必不可少的。本
节中对汇编程序文档书写的标准进行讨论。
如果一个程序员按照系统分析的要求编写并调试好了一个程序,然而并未提供足够的文
档时,我们会发现程序功能常被误用、程序不易阅读和维护。编写并调试好了一个程序并不
意味着程序开发工作全部结束。那么,怎样才能算是开发完全结束呢?必须要满足以下几点
要求:
①.程序完成的功能有完整的、详细的及精确的描述;
②.具备描述程序逻辑的流程图;
③.在程序清单中标有详细的有步骤的算法,该算法将代码的独立功能片断同流程图中
的相应块关联;
④.在必要处解释程序中的单个指令功能。
在过去及现在,不断地有汇编程序员转移到使用高级语言编程。即使汇编程序不断地在
存贮及时间方面更有效,这种转移也在发生。转移的主要原因在于同高级语言相比较,以汇
编写成的程序更难使用、维护和修改。汇编程序所遇到的这一困难的原因除了汇编语言本身
的可读性要低于高级语言以外,主要来自于大部分程序文档不充分。如果这些程序刚写好就
有足够文档的话,用户花在使用、维护、修改以及重新编码方面的高昂代价就可能省下。
尽管许多程序将用高级语言而非汇编语言编程是事实,然而正如前面我们提到的那样,
需要应用汇编语言的场合还是存在的,也就是说,我们还要继续编写汇编语言程序。在编写
汇编语言程序时如下的程序文档标准是非常有用的:

1.应该为每个程序准备描述其逻辑的流程图。这些图应该完整地画出并应同流程图惯
例一致;如果程序包括子程序,应该为每个子程序画出一系列独立的流程图。
2.在程序中每个子程序的开始,应出现详尽的注解:
①.子程序功能的描述,这一描述应足够完善以使任何合格的程序员能够很快确定
子程序的目的。
②.入口参数的描述。例如,如果假定特定寄存器要包含特定参数,这些假定必须
陈述出来。
③.出口参数的描述。例如,如果任何寄存器将被子程序所改变,这些改变应该描
述出来。
④.一个有步骤的算法,每一步骤应同流程图中的步骤相关联,每一步骤应以可读
的自然语言,例如英语依次描述相关代码段的目的。
3、在过程各指令间,算法的各步骤应由合适的注释项来分隔开。在本教材中,采用如
下形式:
*
*** <STEP n>
*
4、每一条定义存贮区域的语句应有包含该区域用法描述的注释。

- 102 -
5、单个语句合适的注释。注意并不是每一条指令语句都需要注释,应该在关键处使用。
这样的注释应该表达不能从指令自身推出的信息。例如,以下行
AR R3,R4 将寄存器R4加至R3
没给读者提供任何比指令自身更多的信息。而以下指令:
AR R3,R4 将数加至总数
表达了有用的不能从指令本身导出的信息。

遵循这些文档标准似乎会导致程序员增加许多不必要的工作。如果只考虑程序员完成一
个程序所花的时间的话,这的确是真的。然而,必须考虑这样的事实:一个程序在四至五年
内会经常用到,需要经常性的维护以及多多少少周期性的改变。很清楚,遵守严格的文档标
准将导致花在这些操作上时间大为减少。大多数情况下,程序的合适文档会导致程序员的时
间最终节省。希望读者牢记并遵守这一标准。

第二节 存储器-立即数类(SI)指令示例

在这一节中,将介绍一种以前未涉及过的SI指令格式以及这种编码格式的MVI和CLI指
令。SI格式是涉及存贮区域和单个字节的指令代码,这一字节称为立即数,该字节实际产生
于指令编码自身(而不是来自寄存器或内存单元)。
SI指令的符号形式为:
标号 助记符 D1(B1)
,I2
SI指令编码格式为:

hOhOhIhI hBhDhDhD
其中: hOhO为操作码
hIhI为立即数
hB为基地寄存器
hDhDhD为偏移地址
注意到地址的编码格式必须为D(B),不要说明变址寄存器。

1.移动立即数指令MVI
移动立即数指令MVI的格式为:
MVI D(B), byte (移动立即数)
功能:(D+(B) )← byte

本指令的功能是将指令中给出的长度为一个字节的立即数送入地址所标示的内存单元
中。指令的操作码为92,本指令的执行不影响条件码CC。
例如指令
MVI 42(R15),C'$'
将$符号对应的EBCDIC编码即5B送入42(5)所表示的内存单元中,本指令的编码为925B
F02A。
又例如,指令:
MVI PRNTLINE,C' '

- 103 -
传送一个空格到PRNTLINE的第一个字符。
SI指令的立即数字节几乎总是以下述三种方式之一说明:
⑴.如果立即字节是一个字符,应该通过将一个字符用单引号括起来并在前面放字母C
说明,在以下例子中:
MVI 42(R15),C'$'
立即字节说明为字符$。
⑵.立即字节可以说明为一个两位的十六进制常量,这可以通过将两个十六进制数字用
单引号括起来在其前面放一个字母X实现。因此,指令:
MVI 42(R15),X'5B'
产生同如下指令一样的代码:
MVI 42(R15),C'$'
⑶.立即字节可以说明为一个八位的二进制常量,这种方法要将一个立即字节说明为一
个8位二进制数字,以单引号括起来并在其前面放一个字母B。以这种形式产生同
先前例子一样指令编码的指令为:
MVI 42(R15),B'01011011'

2.立即数逻辑比较指令CLI
立即数逻辑比较指令CLI的格式为:
CLI D(B),byte (立即数逻辑比较)

指令的执行将比较立即数字节及D(B)中的字节,设置条件码CC以反映比较结果。在比
较中,两个操作数处理为无符号二进制数(即逻辑数),即X'00'为最小值,X'FF’是最大值,
这样的比较称为逻辑比较。条件码设置如下:
CC 含义
0 操作数相等
1 第一个操作数小于第2个
2 第一个操作数大于第2个
3 --
作为解释这两条指令的例子,考虑如下代码段,该段代码简单地将CARD中的空格替换
为0。

LA R4,CARD 取得CARD的地址
LA R2,1 R2作为增量寄存器,增量为1
LA R3,CARD+79 R3作为界限寄存器
SCAN CLI 0(R4),C' ' 寻找空格字符
BNE BUMP 不是则跳过
MVI 0(R4),C'0' 是则替换
BUMP BXLE R4,R2,SCAN 循环

在SI格式中,MVI和CLI几乎是最有用的和最常用的指令,请读者注意掌握。

- 104 -
第三节 存储器-存储器类(SS)指令示例

本节介绍另一类指令格式SS格式,SS格式用于编写涉及两个存贮区域的指令。在此讨
论两个SS指令MVC和CLC,它们的功能于前一节讨论的MVI和CLI相对应。
SS格式有两种。一种两个操作数长度相同,另一种两个操作数长度不同。为了便于讨
论,先讨论第一种。
SS格式的符号形式如下
标号 助记符 D1(L,B1), D2(B2)

其中: D1(B1) 第一个操作数的地址


D2(B2) 第2个操作数的地址
L 每个操作数的长度
编码格式相应为:

hOhOhLhL hB1hD1hD1hD1 hB2hD2hD2hD2


其中: hOhO操作码
hLhL操作数的长度
hB1hD1hD1hD1第一操作数的地址
hB2hD2hD2hD2第二操作数的地址
注意:编码格式中的长度比操作数的实际长度小1。这一编码的长度称为长度代码,因
长度代码比操作数的长度小1,故操作长度可以说明为1到256,当用SS指令时,这一点非常
重要。

1.移动字符串指令MVC
移动字符串指令MVC的格式为:
MVC D1(L,B1),D2(B2) (移动字符串)
功能:
(D1+(B1) ) ← (D2+(B2))
L个字符

指令的执行用开始于D2(B2)的L字节替换开始于D1(B1)的L字节的内容。L个字节的内容
每次改变一个,从左边开始。如果域不重叠的话,这一事实是不重要的,但当域重叠时就特
别重要。本指令的操作码为D2。指令执行不改变条件代码CC。
例如指令:
MVC 14(8,R12),0(R1)
产生编码D207 C00E 1000。
SS语句可以使用隐式地址。这里,正如RS和SI指令中的情形一样,隐式地址中不说明
变址寄存器。当用一个隐式地址说明第一个操作数时,通过将其放在第一个操作数括号中说
明长度。这是一个可能产生混淆的地方。例如,指令:
MVC FIELD1(15),FIRLD2
说明操作数的长度为15,第一个操作数的地址为FIELD1,而不是R15内容的偏移。SS指令
中的长度说明允许为0,如果这样做了,生成的长度代码为0。因此,长度为0和为1的说明将
产生相同的长度编码,例如下面两个语句产生的效果是一样的:

- 105 -
MVC FIELD1(0),FIELD2
MVC FIELD1(1),FIELD2
程序中的标号同一个长度属性关联。如果在SS指令中没有说明长度,第一个操作数的
长度属性将用作缺省长度。例如,如果语句
MVC FWORD,(R2)
……
FWORD DS F
将产生编码D203 F0XX 2000。
MVC是一条特别有用的指令,如下两个例子解释其最普遍的用法。
⑴.程序常见的一个做法是用一个存贮区收集打印信息。在程序执行的不同阶段,各种
不同的信息可能占用这一区域。通过使用MVC指令移动不同的域到这一存贮区完成收集。
例如,指令序列:
MVC PRNTLINE(13),=C' 0THE TOTAL IS'
XDECO R2,PRNTLINE+13
XPRNT PRNTLINE, 25
将打印R2中存放的总数,此前插有合适的字符串。
⑵.可以改变存贮区域的内容以便使每个字节存放一个空格,这可通过一条MVC指令
完成:
MVC PLINE,=CL133' '
然而,这一指令的执行需要133个字节的空格空间。我们有更好的方法:这种方法涉及MVC
重叠操作数区域的使用。这一方法如下:
MVI PLINE,C' '
MVC PLINE+1(132),PLINE
MVI的执行将改变PLINE第一个字节的内容为40(空格)。图6-1描述了受MVC指令执行影响
的动作序列。
第2操作数

第1操作数

图6-1 MVC操作数区域重叠示例

这个过程可描述如下:
①.第2个操作数的第1个字节移动到第1个操作数的第1字节。即PLINE+0中的空格被
移动到PLINE+1;
②.第2个操作数的第2字节PLINE+1(现为空格)移动到第1操作数的第2字节PLINE+2;
③.继续这一过程,每次移动一个字节,直到PLINE行仅仅包含空格。
这是一个将打印置空的标准技术,读者应熟悉这种用法。

2.逻辑比较指令CLC
逻辑比较指令CLC的格式如下::

- 106 -
CLC D1(L,B1),D2(B2) (逻辑比较)

指令的执行完成两个操作的逻辑比较。指令的操作码为D5。记住,在一个逻辑比较中,
两个操作数都被当作无符号二进制数对待。基于这一比较结果设置条件码,条件码具有如下
意义:
CC 含义
0 操作数相等
1 第1个操作数小
2 第1个操作数大
3 --

第四节 地址计数器的访问

现在,读者应该已经非常清楚汇编程序的主要功能即是将源代码转换为可执行的形式。
这一过程涉及的事情之一就是确定所有指令、常量、存贮区域以及符号常数的相对地址,这
些地址通过汇编程序中的部件——地址计数器的值来计数。
在处理任何给定的汇编源代码之前,地址计数器的值初始化为0。因此,在每条指令处
理之后,地址计数器增加该条指令所产生的目标代码长度。当遇到一个指令时,地址计数器
包含的值显示在汇编产生的列表输出的指令左边。当然,如果不存在访问和控制地址计数器
值的机制,程序员对地址计数器的值是没什么兴趣的。

1.引用地址计数器的值
IBM S/390汇编语言中提供了访问地址计数器的手段。为了访问地址计数器的值,在源
程序中可以使用星号。在给定程序中星号的值将是汇编到该指令时地址计数器的值。因此,
语句:
B *+8
L R1,0(R2)
等价于
B PASTLOAD
L R1,0(R2)
PASTLOAD DS 0H
*+8为分支指令产生前地址计数器的值加8。使用PASTLOAD DS 0H在代码中仅
仅建立一个位置,它不产生任何目标代码。
类似地,指令:
CLI 0(R1),C' '
BE *+12
LA R1,1(R1)
B *-12
等价于
LOOP CLI 0(R1),C' '
BE HIT
LA R1,1(R1)

- 107 -
B LOOP
HIT DS 0H
访问地址计数器的一个主要优点就是避免产生太多的标号列表。但是,这样做也有副作
用:如果以后将指令添加到程序中,这样引用会导致错误。例如,指令:
CLI 0(R1),C' '
BE *+12
LA R1,1(R1)
LA R2,1(R2)
B *-12
决不等价于
LOOP CLI 0(R1),C' '
BE HIT
LA R1,1(R1)
LA R2,1(R2)
B LOOP
HIT DS 0H
为了避免这样的错误,无论何时这样引用地址计数器或包含这样引用的程序被修改时都
要特别小心,一个好的限制尺度是*+12或*-12。

2.控制地址计数器的值
ORG指令能改变地址计数器的值,指令格式为:
ORG [addres]

方括号用于表示操作数是可选的。如果指定了操作数,地址计数器的值设置为同这个地
址相关的值。如果没有指定操作数,地址计数器的值被设定为该ORG指令位置的地址。例
如:

HDR1 DC C'1' 回车控制


DC CL38' '
ORG HDR1+15
DC C'CLASS LIST FOR'
HDRDEPT DS CL4 系名
DC C' '
HDRCOURS DS CL4 课程
ORG

可用于定位特定的标题列,注意ORG指令用于定位一个常量以及系名和课程的存贮区域。
这一技术特别有用,因为改变标题的空间只需改变ORG指令。
下面这个例子解释了ORG指令对内存中的同一区域指定不同的标号:

CARD DS CL80
ORG CARD
SOCSEC# DS CL9
NAME DS CL20

- 108 -
SAMPLE# DS CL4
SEX DS CL1
HAIR DS CL2
EYES DS CL2
ORG CARD
SAMPLE DS CL9
#MALES DS CL4
#FEMALES DS CL4
ORG

在这种情况下,说明了80个字符区域的三个可替换的定义。因此,NAME及#MALES引用
的是同一存贮区域,两种情况下,引用的域都定位于CARD+9。
程序6-1解释了出现于本章指令的用法,这一程序是为了产生一个“班级清单”。该程序
的输入假定为记录集,其记录格式如下:
Record列 列的内容
1-4 授课系
5-8 课程编号
9-18 学生的姓
19-28 学生的名
29-80 未用
进一步假定本文件已基于记录的前28列按升序排列。该程序为每门课产生一个班级清单,包
括班中注册学生的总数。应对程序6-1中的如下语句予以特别注意:
CARD DS 0CL80 当前卡片
DEPTN DS CL4 授课系
COURSIN DS CL4 课程编号
LNAMEIN DS CL10 学生的姓
FNAMEIN DS CL10 学生的名
DS CL52
因重复因子为0,CARD语句定义保留0存贮空间,这一语句的作用在于指明紧接着的80个字
符给出一个关于CARD每个域的更详细的描述,这一技术也用于为打印行保留存贮空间,在
上述情况下,得到的好处在于可通过有意义的标号访问子域。例如CARD+8以及LNAMEIN
引用同样的地址。

TITLE ‘ 产生班级清单的程序 ‘
**********************************************************************************************
* 本程序用来产生班级清单。程序接受如下格式的数据输入:
* 1-4列 开课的系
* 5-8列 课程代号
* 9-18列 学生的姓
* 19-28列 学生的名
* 29-80列 未用
* 假定输入记录已经根据1-28列的内容按升序排列。程序产生为每一门课程产生
* 一个单独的(换新页)班级清单(即选课学生名单)。每个清单的最后有小计
* 行统计有多少人选课。程序的逻辑如下所示:

- 109 -
* 步骤1:调用READCARD读入第一个记录并设置结束标记EOFFLAG;
* 步骤2:若输入没有结束,转步骤4;
* 步骤3:打印错误信息(无输入)。转步骤8;
* 步骤4:清打印行;
* 步骤5:若输入结束(没有更多的清单需要打印) ,转步骤7;
* 步骤6:调用PROCLASS打印班级清单;
* 步骤7:打印“成功结束”信息;
* 步骤8:退出
**********************************************************************************************
*
CLASSLST CSECT
USING CLASSLST,R15
*
***<步骤1> 读入第一个记录并设置EOFFLAG
* BAL R11,READCARD 读第一个记录
***<步骤2> 若有输入,转步骤4打印清单
*
CLI EOFFLAG,C’Y’
BNE CLEARLN 若没有结束跳转
*
***<步骤3> 打印“没有输入”信息并推出
*
XPRNT =C’1***NO INPUT ***’,17
B EXIT
*
***<步骤4> 清打印行
*
CLEARLN MVI PRNTLINE,C’ ‘
MVC PRNTLINE+1(132),PRNTLINE
*
***<步骤5> 重复调用PROCLASS直到输入结束
*
PROCLOOP CLI EOFFLAG,C’Y’ 结束了吗?
BE PRINTEND
*
BAL R11,PROCLASS 打印一个班级清单
*
B PROCLOOP
*
***<步骤6> 打印“成功结束”信息
*
PRINTEND XPRNT =C’1*** SUCCESSFUL TERMINATION ***’,31
*
***<步骤7> 退出

- 110 -
*
EXIT BR R14
*
**********************************************************************************************
* 这是READCARD子程序。
* 本程序用来读入一个记录。若遇到输入结束,设置结束标记EOFFLAG为‘Y’
* (该值被初始化为‘N’,所以在未结束前将一直是‘N’)。
* 程序的逻辑如下:
* 步骤1:读下一个记录;
* 步骤2:若输入结束,置EOFFLAG为‘Y’;
* 步骤3:退回到调用程序;
**********************************************************************************************
*
READCARD STM R0,R15,READSAVE 保存调用程序的寄存器
*
***<步骤1> 读下一个记录
*
XREAD CARD,80
*
***<步骤2> 若输入结束,置EOFFLAG为‘Y’
*
BC B’1011’,READEXIT 没有结束则跳转
*
MVI EOFFLAG,C’Y’ 置EOFFLAG指示器
*
***<步骤3> 返回调用程序
*
READEXIT LM R0,R15,READSAVE 恢复调用程序的寄存器
BR R11 返回调用程序
*
READSAVE DS 16F 寄存器保存区域
*
**********************************************************************************************
* 这是PROCLASS子程序。
* 本程序打印一个班级清单。当是数据输入结束(EOFFLAG值为‘Y’)或者是
* 发现读入了下一门课程的记录时,打印结束。程序的逻辑如下:
* 步骤1:从输入记录中保存系及课程信息。这两个域代表着当前的清单关
* 键字—它们标示着当前的清单应该打到哪里,即当发现新的关键
* 字时打印结束;
* 步骤2:设置学生人数计数器为0;
* 步骤3:调用PRINTHDR为班级清单的第一页打印页及栏目标题;
* 步骤4:检查是否输入结束,或关键字发生了变化。若有二者之一,打印
* 结束,转步骤9;
* 步骤5:建立打印行;

- 111 -
* 步骤6:调用PRINTSP1打印一行(若遇到换页,还要打印页头),打印
* 完毕之后打印行将被清空;
* 步骤7:学生人数计数器值加1;
* 步骤8:调用READCARD读下一个记录,转回步骤4;
* 步骤9:打印最后一行,改行给出一共有多少学生的信息;
* 步骤10:返回调用程序。
**********************************************************************************************
*
PROCLASS STM R0,R15,PRCLSAVE 保存调用程序的寄存器
*
***<步骤1> 保存当前的关键字
*
MVC CURRKEY,CARD
*
***<步骤2> 设置学生人数计数器初值为0
*
SR R2,R2 R2为学生人数计数器
*
***<步骤3> 打印清单的头部信息
*
BAL R11,PRINTHDR
*
***<步骤4> 检查是否打印结束(输入结束或遇到新的关键字)
*
PROCCARD CLI EOFFLAG,C’Y’
BE PRINTTOT 输入结束跳转
CLC CURRKEY,CARD
BNE PRINTTOT 遇到新关键字跳转
*
***<步骤5> 建立打印行
*
MVC DEPTOUT,DEPTIN 将输入数据送到打印行
MVC COURSOUT,COURSIN
MVC LNAMEOUT,LNAMEIN
MVC FNAMEOUT,FNAMEIN
*
***<步骤6> 调用PRINTSP1打印此行
*
BAL R11,PRINTSP1 打印此行,若遇到换页则还要
* 打印页头信息
*
***<步骤7> 学生人数计数器加1
*
LA R2,1(R2)

- 112 -
*
***<步骤8> 读下一个记录
*
BAL R11,READCARD
*
B PROCCARD
*
***<步骤9> 打印最后一行统计信息
*
PRINTTOT XDECO R2,PRNTLINE+25 把总数送入打印行
MVC PRNTLINE+7(18),=C’TOTAL # STUDENTS =’
MVI PRNTLINE,C’0’ 设置打印控制符
*
XPRNT PRNTLINE,133 打印
MVI PRNTLINE,C’ ‘ 清打印行
MVC PRNTLINE+1(132),PRNTLINE
*
***<步骤10> 返回调用程序
*
LM R0,R15,PRCLSAVE 恢复调用程序的寄存器
BR R11 返回
*
PRCLSAVE DS 16F 寄存器保存器
*
**********************************************************************************************
* 这是PRINTHDR子程序。
* 本程序打印页及栏目的标题后设置打印行计数置为5(即下一个打印行从第5行
* 开始)。程序的逻辑如下:
* 步骤1;将系和课程信息放入头行;
* 步骤2:打印页头(两行) ;
* 步骤3:设置打印控制以便下一次打印时空出两个空行;
* 步骤4:打印行计数器值设为5(即下一个打印行从第5行开始);
* 步骤5:返回调用程序;
**********************************************************************************************
*
PRINTHDR STM R0,R15,HDRSAVE 保存调用程序的寄存器
*
***<步骤1> 将系和课程信息放入头行
*
MVC HDRDEPT,CURRKEY 从CURRKEY中取出系和课程
MVC HDRCOURS,CURRKEY+4 信息
*
***<步骤2> 打印页头(两行)
*

- 113 -
XPRNT HDR1,39
XPRNT HDR2,43
*
***<步骤3> 设置打印控制
*
MVI PRNTLINE,C’0’
*
***<步骤4> 设置打印行计数器值为5
*
MVC LINECTR,=F’5’
*
***<步骤5> 返回调用程序
*
LM R0,R15,HDRSAVE 恢复调用程序寄存器
BR R11 返回
*
HDRSAVE DS 16F 寄存器保存区域
*
HDR1 DC C’1’ 页头第一行
DC CL38’ ‘
ORG HDR1+15
DC C’CLASS LIST FOR’
HDRDEPT DS CL4
DC C’
HDRCOURS DS CL4
ORG
*
HDR2 DC C’0’ 页头第二行
DC CL42’ ‘
ORG HDR2+4
DC C’DEPT’
ORG HDR2+9
DC C’COURSE’
ORG HDR2+19
DC C’LAST NAME’
ORG HDR2+33
DC C’FIRST NAME’
ORG
*
**********************************************************************************************
* 这是PRINTSP1子程序。
* 本程序用于在一个空行之后打印输出行。若行计数器值到达50,在打印之前
* 换页,并打印页头信息,空两行之后继续打印。程序的逻辑如下:
* 步骤1:若行计数器值小于等于50,转步骤3;

- 114 -
* 步骤2:调用PRINTHDR换页,打印页头;
* 步骤3:打印该行;
* 步骤4:行计数器置加1;
* 步骤5:清打印行;
* 步骤6:返回
**********************************************************************************************
*
PRINTSP1 STM R0,R15,PRSPSAVE 保存调用程序的寄存器
*
***<步骤1> 检查是否要换页
*
CLC LINECTR,=F’50’
BNH NOWPRINT 若不是新页则跳转到步骤3
*
***<步骤2> 调用PRINTHDR换叶并打印页头
*
BAL R11,PRINTHDR
*
***<步骤3> 打印该行
*
NOWPRINT XPRNT PRNTLINE,133
*
***<步骤4> 行计数器加1
*
L R3,LINECTR
LA R3,1(R3)
ST R3,LINECTR
*
***<步骤5> 清打印行
*
MVI PRNTLINE,C’ ‘
MVC PRNTLINE+1(132),PRNTLINE
*
***<步骤6> 返回调用程序
*
LM R0,R15,PRSPSAVE 恢复调用程序的寄存器
BR R11 返回
*
PRSPSAVE DS 16F 寄存器保存区域
*
**********************************************************************************************
*
CURRKEY DS CL8 关键字存放区
*

- 115 -
CARD DS 0CL80 当前输入记录
DEPTIN DS CL4 系
COURSIN DS CL4 课程
LNAMEIN DS CL10 学生姓
FNAMEIN DS CL10 学生名
DS CL52
*
EOFFLAG DC C’N’ 结束标志
*
LINECTR DS F 打印行计数器
*
PRNTLINE DS CL133 打印行
ORG PRNTLINE+4
DEPTOUT DS CL4 系
ORG PRNTLINE+10
COURSOUT DS CL4 课程
ORG PRNTLINE+19
LNAMEOUT DS CL10 姓
ORG PRNTLINE+33
FNAMEOUT DS CL10 名
ORG
*
LTORG
R0 EQU 0
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6
R7 EQU 7
R8 EQU 8
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
*
END CLASSLST

程序6-1

- 116 -
第五节 逻辑运算和比较指令

为了理解本节即将介绍的指令的意义,首先有必要理解计算机是如何执行加法和减法
的。为了解释所涉及的过程,考虑特定的例子将会大有帮助,在这些例子中,将会使用四位
而不32位整数。简化的动机将会很清楚,如果读者偿试一下32位整数算法便可得知。可以采
用如下步骤将两个有符号二进制数相加:
1、将最右边的进位设置为0。
2、从最右边的数位开始,将相应的两个数位及进位相加,确定结果是何数。如果加的
结果产生了0和1,这就成为相应的结果位,下一位的进位置为0;如果加的结果产生了10或
11,则将0或1写成相应的结果位,下-位进位置为1。
3、最左边两位加法的进位不改变结果的值,然而,如果产生的进位不同于进入最左位
置的进位,就产生了溢出。因此,要加1011和0010,以下步骤将被执行:
进位 0010
1011
+ 0010
—————
结果 1101
在这种情形下,2加到-5上得到结果-3,因为最左的两位进位一致,没有产生溢出。如下例
子更完善地解释相加过程。
溢出 yes no yes no
进位 1000 1111 0110 0001
1111 1101 0111 0101
1000 1111 0010 0001
———— ——— ——— ———
结果 0111 1100 1001 0110
用加法术语很容易解释减法,为了从一个数m减去数n,将-n加至m便可。例如,为从0010
中减去1101。
⑴.将1101转换为其补0011
⑵.将0010和0011相加,得出0101
如果-n和m相加时产生了溢出,则减去产生了溢出。

现在可以讨论逻辑运算指令AL、ALR、SL、SLR、CL和CLR指令了。这些指令都有相
应的算术运算指令,其相应关系如下表所示。
指令 格式
加法 A r, D(X,B)
逻辑加法 AL r, D(X,B)
寄存器加法 AR r1, r2
寄存器逻辑加法 ALR r1, r2
减法 S r, D(X,B)
逻辑减法 SL r, D(X,B)
寄存器减法 SR r1, r2
寄存器逻辑减法 SLR r1, r2

- 117 -
比较 C r, D(X,B)
逻辑比较 CL r, D(X,B)
寄存器比较 CR r1, r2
寄存器逻辑比较 CLR r1, r2

以下分析逻辑运算指令与算术运算指令的不同之处:
⑴.AL/ALR指令与A/AR指令的不同之处在于:
①.如果AL和ALR指令执行结果出现了溢出,溢出不会引起程序的终止。
②.条件码设置如下
CC 含义
0 结果为0,最左进位为0
1 结果非0,最左进位为0
2 结果为0,最左进位为1
3 结果非0,最左进位为1
⑵.SL/SLR指令与S/SR指令的不同之处在于:
①.如同AL和ALR,溢出不会引起程序的终止。
②.条件码设置如下
CC 含义
0 -
1 结果非0,最左进位为0
2 结果为0
3 结果非0,最左进位为1
⑶.除了操作数被看成32位无符号整数外,CL/CLR指令与C/CR指令类似。
尽管算术及比较逻辑指令偶然用于无需指明溢出的情形,它们的主要用途在于构造扩展
精度的操作,即用这些指令构造用以加、减、比较任意大整数的程序。作为一个例子,完成
加、减,比较64位有符号整数的过程在程序6-2中给出,注意可以构造类似的过程以操作任
意大小的整数。

* 本程序把两个内存双字相加,两个双字的地址分别放在R3和R4中。
* 使用R10作为连接寄存器
*
ADDRT LM R0,R1,0(R3) 取操作数1
AL R1,4(R4) 加操作数2右部的字
BC B'1100',ADDLEFT 若无进位则跳转
A R0,=F'1' 有进位则加1
*
ADDLEFT A R0,0(R4) 加左部的字
STM R0,R1,0(R3) 存结果到操作数1中
BR R10 返回

* 本程序把两内存双字相减,两个双字的地址分别放在R3和R4中。
* 相减的结果放入第一操作数中,使用R10作为连接寄存器。
*
SUBRT LM R0,R1,0(R3) 取操作数1

- 118 -
SL R1,4(R4) 减操作数2右部的字
BC B'0011',SUBLEFT 若有借位则跳转
BCTR R0,0
SUBLEFT S R0,0(R4) 减操作数2左部的字
STM R0,R1,0(R3) 保存结果到操作数1中
BR R10 返回

* 本程序把两内存双字相比较,两个双字的地址分别放在R3和R4中。
* 使用R10作为连接寄存器。
*
COMPRT LM R0,R1,0(R3) 取第一操作数
C R0,0(R4) 比较左边的字
BCR B'0110',R10 若不等则退出
CL R1,4(R4) 比较右边的字
BR R10 返回

程序6-2

习题六

1、给出下列每条指令的编码形式:
a. MVI 0(R3),C'A' b. MVI 37(R10),13
c. MVI 0(R2,R3),B'01010101' d. MVI 125(R1),C'$'
e. CLI 11(R2),C'''' f. CLI 0(R0),12
2、给出下列指令的符号形式:
a. 956C3010 b. 9240D000 c. 957DC004
d. 92D14011 e. 95C43033 f. 9200B008
3、给出下列每条指令的编码形式。你可以假设字段F1出现在从基址寄存器R12偏移为50(十
六进制)处:
a. MVC 0(4,R7),F1 b. MVC F1+3(8),0(R6)
c. CLC 0(3,R6),0(R7) d. MVC F1(8),10(R2)
e. CLC 0(6,R7),12(R2,R3) f. CLC 18(5,R6),F1+2
4、给出下列指令的符号形式:
a. D500C0021001 b. D2FF10081009
c. D2067007800D d. D201E0004007
e. D50FB006A006 f. D505400C800A
5、假设FIELD是包含F1F2F3F4的一个4字节的字段,执行了指令:
MVC FIELD+1(3),FIELD
FIELD的内容如何?
6、重写下列的短代码,不要使用标号LOOP:
LOOPCLC 0(2,R4),=C'##'
BE HIT

- 119 -
BXLE R4,R8,LOOP
7、下面的代码段会正确运行吗?为什么?
SR R5,R5
LA R3,INPUT+132
LA R4,132
CLI 0(R3),C'$'
BNE *+8
LA R5,1(R5)
BCTR R3,R0
BCT R4,*-16
8、假设将写一个程序把读到的输入记录转换成下列格式:
记录的列 列的内容
1-20 客户的姓
21-30 客户的名
31-39 社会保险号码
40-49 电话号码
50-80 未用
写出正确定义数据的行,使用ORG来定位字段。
9、下面的两个DUMP是由于操作异常引起的,从这些DUMP可推断出错误的原因是什么?

DUMP1
PSW:FF000000 DF000066
R0-7: F4F4F4F4 F4F4F4F4 00000036 00000069 00000036 F4F4F4F4 F4F4F4F4 F4F4F4F4
R8-15:F4F4F4F4 F4F4F4F4 F4F4F4F4 F4F4F4F4 F4F4F4F4 000008C4 0000090C 00000000
USER STORAGE:CORE ADDRESSES SPECIFIED-000000 to 000A8C
000000 47F0F078 D1D6C8D5 40E2D4C9 E3C8D4C1 *.00.JOHN SMITHMA*
000010 D9E840D9 C1E8C2E4 C7C5D6D9 C7C540C7 *RY RAYBUGEORGE G*
000020 D9C5D1C5 D9D9E840 D4C9D3C6 D3C9D5C4 *REJERRY MILFLIND*
000030 C140D4C1 C3C5F0D1 D6C8D540 E2D4C9E3 *A MACE 0JOHN SMIT*
000040 C8DC1D9 E840D9C1 E8C2E4C7 C5D6D9C7 *HMARY RAYBUGEORG*
000050 C540C7D9 C5D1C5D9 D9E840D4 C9D3C6D3 *E GREJERRY MILFL*
000060 C9D5C4C1 40D4C1C3 C5004000 4144000A *INDA MACE *
000070 4133000A 47F0F060 4120F004 E000F86C *.....00-..0.\.8%*
000080 00504740 F054D209 2000F86C 5A20F8C0 *.&. 0.K...8%!.8{*
000090 E000F86C 005047F0 F082F5F5 F5F5F5F5 *\.8%.&.00b555555*
0000A0 F5F5F5F5 F5F5F5F5 F5F5F5F5 F5F5F5F5 *55555555555555555*
LINES:0000A0-000850 ARE IDENTICAL
000860 F5F5F5F5 F5F5F5F5 F5F507FE D3C9D5C4 *5555555555..LIND*
000870 C140D4C1 C3C5D240 40404040 40404040 *A MACEK *
000880 40404040 40404040 40404040 40404040 * *
LINES:000880-0008A0 ARE IDENTICAL
0008B0 40404040 40404040 40404040 F5F5F5F5 * 5555*
0008C0 0000000A F5F5F5F5 F5F5F5F5 F5F5F5F5 *....555555555555*
0008D0 F5F5F5F5 F5F5F5F5 F5F5F5F5 F5F5F5F5 *5555555555555555*

- 120 -
DUMP2
PSW:FF000000 DF00084E
R0-7: F4F4F4F4 F4F4F4F4 00000806 00000849 00000806 F4F4F4F4 F4F4F4F4 F4F4F4F4
R8-15:F4F4F4F4 F4F4F4F4 F4F4F4F4 F4F4F4F4 F4F4F4F4 000008DC 00000924 00000000
USER STORAGE:CORE ADDRESSES SPECIFIED-000000 to 000A8C
000000 47F0F860 F5F5F5F5 F5F5F5F5 F5F5F5F5 *.08-555555555555*
000010 F5F5F5F5 F5F5F5F5 F5F5F5F5 F5F5F5F5 *5555555555555555*
LINES:000010-0007C0 ARE IDENTICAL
0007D0 F5F5F5F5 C6D6D9C4 40404040 4040C3C8 *5555FORD CH*
0007E0 C5E5E840 40404040 C3C8D9E8 E2D3C5D9 *EVY CHRYSLER*
0007F0 4040D6D3 E2D4D6C2 C9D3C540 E3D6E8D6 * OLSMOBILE TOYO*
000800 E3C140E3 C5D992F0 F8164130 F8174140 *TA TERK08...8.. *
000810 F7D447F0 F848F0C6 D6D9C440 40404040 *7M.08.0FORD *
000820 40C3C8C5 E5E84040 404040C3 C8D9E9E2 * CHEVY CHRYS*
000830 D3C5D940 40D6D3E2 D4D6C2C9 D3C540E3 *LER OLSMOBILE T*
000840 D6E8D6E3 C140E3C5 D9424780 F882D209 *OYOTA TER...8bK.*
000850 30004000 4144000A 4133000A 47F0F848 *.. ..........08.*
000860 4120F7D4 E000F884 00504740 F806D209 *..7M\.8d.&. 8k.*
000870 2000F884 5A20F8D8 E000F884 005047F0 *..8d!.8Q\.8d.&.0*
000880 F86A07FE E3D6E8D6 E3C140E3 C5D9C3C5 *8|..TOYOTA TERCE*
000890 D3404040 40404040 40404040 40404040 *L *
0008A0 40404040 40404040 40404040 40404040 * *
LINES:0008A0-0008C0 ARE IDENTICAL
0008D0 40404040 F5F5F5F5 0000000A F5F5F5F5 * 5555....5555*
0008E0 F5F5F5F5 F5F5F5F5 F5F5F5F5 F5F5F5F5 *5555555555555555*
10、假设两个串S1和S2是相匹配的,即每个相应的字符都是相似的。两个字符是相似的,要
么它们均相同,要么其中一个字符为“?”号,写一个读入输入记录集合的程序,每个
输入记录遵守下面的格式:
位置 内容
1-10 模式串
11-80 数据串
对每条输入记录,程序必须在数据串中寻找一个长度为10字符与模式串相似的子串,当
找到了这样的子串就打印出模式串、子串和子串的位置(在数据串中的偏移位置),程序
应当使用子程序。
11、写一个程序读入两串单词,将它们合并为一串单词,每串至多包含50个按字母顺序排列
的英文单词,并且每个单词至多包含8个字母,单词必须从符合以下规格的记录中读取。
a. 这里有两个记录集,每串一个记录集,第二个串的记录紧跟在第一个串的记录之后。
b. 集合中的每个记录,可能除了最后一个记录外,都将包含五个单词,从第10、20、30
和50列开始。
c. 集合中的最后一个记录可包含一到五个单词。
d. $符号紧跟在记录集合中最后一个单词之后,即在18、28、38、48或58列上。程序应
当包含三个子程序:一个子程序读入记录集合并建立单词表,一个用来显示单词表中
输入单词的内容,一个用来完成将两个表合并为一个表。

- 121 -
主程序的编写步骤应如下:
第一步:读入第一个表;
第二步:显示第一个表;
第三步:读入第二个表;
第四步:显示第二个表;
第五步:合并两个表;
第六步:显示合并后的表。
假设头每个表不含相同的表项(单词)。假如一个单词出现在两个表中,只应将一个单
词插入到合并的表中。
12、编写一个完成以下任务的程序:
a. 读入一些记录卡片,每个记录包含零到六个的英语单词,每个单词包含至多10个字
符并且从第1、11、21、31、41或51列开始。因而,每个记录卡片将包含六个10个字
符长的字段,在最左边位置为空白的任何字段将被忽略,将单词存入一个表格中。
b. 显示构造的表格内容。
c. 将表格中的单词以字典顺序排序。
d. 显示排序后表格的内容。
为了对表格排序,可使用与3.6节末尾练习5讨论的相似算法,在那个例子中。虽然,表
格中的输入按降序排列,可利用以下技术实现表格的增序排列。
第一步:找到表格未排序部分(开始时都未排序)的“最小”表项(单词)。
第二步:把最小表项与未排序部分第一表项交换,未排序部分表项减少。
第三步:若不止一个表项留在表格未排序部分,重复这个过程完成那部分表格的排序。
13、假设一个班的全部学生拥有下列格式的个人信息卡
信息卡的列 列的内容
1-20 学生姓名
21 学生身份编码
1-freshman
2-sophomore
3-junior
4-senior
22-25 学生专业
26 此处出现非空格字符,该生为旁听生。
编写一个读取记录并且产生以下报告的程序。
CLASS REPORT
FRESHMEN SOPHOMORES JUNIORS SENIORS
NAME MAJOR NAME MAJOR NAME MAJOR NAME MAJOR
JOHN DORN ENGL
ELIZABETH DOE PSYC
MARYCALLEY MATH
…… …… …… ……
…… …… …… ……
…… …… …… ……
在报告中应注意以下方面:
a. 一个学生的名字应当打印在他或她的身份给出的恰当列上。
b. 假设一个学生旁听了某门课程,应紧靠他或她姓名左边打印一个*。

- 122 -
c. 每页应打印50名学生的信息,标题应出现在每页中。若有80名学生的话,应打印两
页,每一页应包含标题。
用一个子程序来打印一行是很方便的,子程序应使用一个变量LINECNT,初始值为51并
使用以下逻辑:
第一步:若LINECNT小于等于50,转到第三步。
第二步:打印页标题,将LINECNT置为1,使用一个0回车控制符,产生一个空行打印报
告一行,退出。
第三步:使用一个空格回车控制符,打印报告一行,将LINECNT加1,退出。
14、许多商店为它们的销售准备了发票,发票形式如下:
Item #Units Cost/Unit Cost
0421 2 1.14 2.28
3841 1 14.00 14.00
Total=16.28
假设一家商店想用计算机来验证发票的计算结果,编写一个这样的程序,程序应将获得
的记录转换成下列规格。
a. 每张发票有一个记录卡片集合,这个集合由每行一项构成的记录卡片及包含了合计
数的记录卡片组成。
b. 发票的所有项目记录卡片出现在合计数记录之前并且转换成格式:
记录卡片列号 列的内容
1 1
2-5 项目编号
6-8 销售件数
10-15 每件单价
17-23 小记
c. 一张发票的总记记录将跟在卡片项目之后且转换成以下格式:
列的位置 列的内容
1 2
2-10 合计
对每张发票,打印下列内容:
LINE ITEMS 1421 4 100 400
2981 2 109 216*
0410 1 19100 19100
TOTAL=19716
如果项目记录的任何字段只包含空格或者价钱不正确,应在价钱的右边打印一个星号。
同样的,假如合计不准确,也在它的右边打印一个星号。
15、编写一个程序完成以下任务:
a. 读入一系列记录,每个记录包含了长度至多为10个字符0个到六个英文单词,单词从
第1、11、21、31、41或51列开始。因而,每个记录卡片将包含六个10字符构成的字
段,当读到字段在第1到3列包含了END的记录,将所有包含了单词的记录放入第一个
表中。
b. 显示第一个表的内容。
c. 读入一系列的记录并且创建第二个表。
d. 显示第二个表的内容。
e. 创建第三个表,它仅包含前面创建的两个表中都出现的单词。

- 123 -
f. 显示第三个表的内容。
假设任何每个表包含的单词不超过30个,每个表不含有相同的单词。
16、编一个程序打印四项构成的姓名和地址。程序读取记录,每个记录包含了一个姓名和地
址。每条记录包含三到五行,姓名行和地址行等均为15个字符长,忽略每条记录的最
后五个字符。例如,下面是有效的记录数据:
JOHN ANDERSON 1527 STATE ST. MORGANTOWN OHIO
ROBIN BENNIT P.O.BOX 156 KINROSS MICHIGAN
J.D.DORSCH GODFREY LANE JASPER,ILL.
MARY LARSON 375 R.RT.18 BAKER ARKANSAS
BOB SMITH 3155 FIRST ST APT 3B JACKSON FLA
程序必须同时打印四条记录中的数据,四个记录的第一行前应当空一行,不打印姓名行
和地址行等任何空白行。对上述输入数据,打印行应当如下:

JOHN ANDERSON ROBIN BENNIT J.D.DORSCH MARY LARSON


1527 STATE ST. P.O.BOX 156 GODFREY LANE 375 R.RT.18
MORGANTOWN KINROSS JASPER,ILL. BAKER
OHIO MICHIGAN ARKANSAS

BOB SMITH
3155 FIRST ST
APT 3B
JACKSON
FLA
程序至少应当包含下面的子程序:
z READSET四个一组读入记录到一个指定区域,它返回读到的记录数目(也可能为零);
z PRINTSET 打印一组记录,它使用子程序FILLUP;
z FILLUP创建一个打印行,它以记录和行号(1到5)数组为输入。

- 124 -
第七章 外部子程序设计

第一节 外部子程序介绍

通过前面的介绍和例程的展示,至此,读者对通过使用汇编子程序得到的好处应有了清
楚的认识,创建在程序任意位置均可以调用的通用子过程可以达到如下目标:
⑴.简化程序的逻辑
⑵.有效地减少所需代码的数量
⑶.使调试任务更加容易
到此时为止,我们已经介绍过的子程序称为内部子程序,因为它们本身是调用它们的程
序的一部分。在这一章中我们将介绍可以被任何程序访问的子程序,这样的子程序称为外部
子程序,它们的创建和维护可以同调用它们的程序分开。
两个重要因素使外部子程序的普遍使用成为可能:
1、源文件(外部子程序的)可以被汇编程序单独汇编以产生一个目标模块。产生的目标
模块进入目标模块库,该库只是一个驻留于某些诸如磁盘阵列的海量存贮设备上的目标模块
集合。
2、加载程序可以以目标模块作为输入,也可以以目标模块库作为输入。从这一输入中,
加载程序构造一个可执行程序,该程序采用目标模块库中任何所需的外部模块。
外部子程序的使用如图7-1所示。

外部子程 主程序源
序源文件 文件

汇编程序 汇编程序

目标模块 目标模块

目标模
块库

装载程序

可执行程序

图7-1 主程序同外部子程序结合示意图

- 125 -
从图7-1我们可以看出外部子程序同主程序的完全无关性。因此,完成常见任务需要的
外部子程序可以独立创建和并放入目标模块库,然后开放给任意的其它程序调用。在本章的
后面我面将介绍一些例子,通过这些例子,读者会明白拥有一个完全调试好的可用子程序的
公有集合是多么的有用。

第二节 外部子程序设计

在前一节中,我们讨论了外部子程序的概要,这里,我们将讨论编码及调用外部子程序
的技术细节。

1.外部子程序的结构和调用方式
每个外部子程序应以如下形式的语句开始:
名字 CSECT

其中名字是一个通过其即可调用该子程序的标号。CSECT定义一个控制段的开始,控
制段是一块可以独立于其它代码块而被汇编或重定位的代码块。目前我们可以把每个控制块
看成是一个独立的子程序。
正如前一节所讨论的,一个子程序调试完毕后,通常将其汇编成一个独立的目标模块。
在这种情况下,子程序的源文件同读者以前所熟悉的源文件在许多方面非常相似。在调试阶
段,几个外部子程序经常与调用它们的主程序存在于同一个源程序文件中。这样的一个源文
件具有如下形式:

MAINPROG CSECT
……
……
LTORG
SUBRT1 CSECT
……
……
LTORG
SUBRT2 CSECT
……
……
LTORG
END MAINPROG

这里,每个子程序的开始由相应的CSECT语句确定。每个新控制段将从一个双字边界
开始。每个控制段的最后必须包含LTORG语句以保证每个子程序的符号常数不放在第一个
控制但进行汇编(上例为MAINPROG),而和它们应该成为其一部分的子程序一起汇编。
从一个主程序或其它外部子程序访问或调用一个外部子程序的问题比内部子程序稍复
杂一些。例如,假定SUBRTN是一个外部子程序,如果SUBRTN独立于调用程序单独汇编,
则语句

- 126 -
BAL R14,SUBRTN
是不能调用SUBRTN子程序的。当汇编程序遇到该语句时,SUBRTN将被看作一个未定
义的符号。基于同样的理由,下面的调用形式也是不可行的:
L R15,=A(SUBRTV)
BALR R14,R15
在这种情况下,如果SUBRTN子程序被单独汇编的话,SUBRTN将再次被汇编程序声明
为未定义的符号。
正确解决这一问题的方法是:我们必须在调用之前就获得SUBRTN的地址,这可以通过
使用V类型地址常量完成,说明一个V类型常量的格式为:
Label DC V(外部符号)

其中外部符号通常是对应的CSECT语句的标号。被引用的CSECT可以出现在同一源文
件中或单独汇编。在上述任何一种情况下,这条语句在汇编时产生值为00000000的全字,而
由装载程序在内存中构造执行程序时填充其所引用的子程序的地址。因此,语句:
SUBADDR DC V(SUBRTN)
程序汇编时将产生00000000,而程序执行时SUBADDR将包含SUBRTN的地址。基于上
述讨论,下列语句:
L R15,SUBADDR
BALR R14,R15
……
SUBADDR DC V(SUBRTN)
可用于调用子程序SUBRTN。更好的方法是通过使用V型符号常量,示例如下:
L R15,=V(SUBRTN)
BALR R14,R15

2.外部子程序的编写细节
一个程序员在编写外部子程序时,自然会考虑如下问题:
z 什么寄存器可以被子程序改变?
z 参数如何传送到子程序?
z 结果如何返回给调用程序?
在子程序完全可以开放使用之前,这些问题必须仔细地编好文档,为了简化创建公用子
程序及方便地调用这些子程序,IBM已指定了一些基本的标准连接约定。这些约定如下:
⑴.当控制交给外部子程序时,R15应包含子程序的第一条指令的地址,R14包含要返
回到调用程序中调用指令之后的指令的地址,因此,如果语句
L R15,=V(SUBRTN)
BALR R14,R15
用于调用SUBRTN,则R14和R15都包含合适的值。
⑵.要传送给子程序的重要参数通常可以通过一个参数表来进行传递。参数表是一系列
连续的全字,其中每个全字包含一个地址。在进入子程序的入口前,R1应包含参数表的地
址。例如,为了将要排序的表的地址及包含表项数目的字段传入外部子程序SORTAB中,应
使用如下代码:
LA R1,PARMLIST
L R15,=V(SORTTAB)
BALR R14,R15

- 127 -
……
PARMLIST DC A(TABLE,NUM)
NUM DC F'20'
参数表的确切格式应包含在为该子程序编写的文档中,如果只有几个参数传入子程序,
就可通过寄存器完成。例如,语句
LM R2,R3,=A(TABLE,20)
L R15,=V(SORTAB)
BALR R14,R15
可用于调用子程序,假定SORTTAB的参数将从R2和R3取出,而不是从参数表取出。在
参数表数目可变的情况下,公认的做法是将参数表最后一个字设置为一个特殊的数字。这使
被调用的子程序很容易地确定参数表中参数的数目。
⑶.子程序要返回到调用程序的值通常存放在参数表指定的区域。然而,如果只返回一
个值,通常通过R0传回。
⑷.进入子程序时,保存寄存器的值并于返回控制给调用程序之前恢复这些寄存器的值
应该是子程序的责任,因为假设调用的是一个由别的程序员编写的外部子程序,在没有看到
其编码清单之前程序员是没有办法知道该子程序修改了什么寄存器的。在调用外部子程序时
的保护和恢复寄存器远比内部子程序要复杂,IBM提出了以下的规范:
寄存器保存区域由调用程序定义,在调用子程序时调用程序应把此存储区域的地址存放
到R13中。子程序利用这一区域来进行寄存器的保护和恢复。寄存器保存区域是一个由18个
连续的全字构成的区域,它的格式如下:
包含的一个18字有效区域寄存器保护区域的格式如图7-2解释。

保留
后向指针
前向指针
R14
R15
R0
……

R12

图7-2 寄存器保护区格式示意图

寄存器保护区域的第二和第三个字的用法在本节稍后加以讨论。第4个字到第18个字用
于保存寄存器14、15、0、1、…、12的内容。因此,语句
STM R14,R12,12(R13)
应是子程序入口处第一条被执行的执行,而语句
LM R14,R12,12(R13)
BR R14
应用于恢复寄存器的内容和返回控制给调用程序。
⑸.因为在子程序调用中经常使用R1,R14和R15,这些寄存器不应被用作基址寄存器。
由于这个原因,每个外部子程序中应包含一段代码,以便在子程序中计算地址建立基址寄存
器。例如,语句
BALR R12,R0
USING BASEPT,R12

- 128 -
BASEPT DS 0H
用于将R12建立为基址寄存器,BALR语句只是将下一条可执行语句的地址放入R12(不
发生转移,因为第二个操作数为R0)。DS语句仅用作建立相应标号BASEPT地址的一种手段,
没有保留任何存贮区域,因为不需要也不能保留。
操作系统初始化执行程序的细节远远超出了本书的内容,然而有一点在这里需要提到。
我们在操作系统下提交运行的主程序从逻辑上来看相当于操作系统调用的外部子程序,所以
操作系统初始化时为主程序提供了保护区域,适用于外部子程序中的寄存器连接约定对于主
程序而言仍然有效。因此,在到此为止的前述所有例子中,我们已经直接用到了这些约定:
R15包含第一条可执行指令的地址并被用作基址寄存器;R14包含操作系统的返回地址并通
过转移到R14中的地址而得以退出程序运行。
为了遵循我们刚才描述的调用约定,每个不调用外部子程序的外部子程序应用一段类似
于下述的代码开始:
ROUTINE CSECT
STM R14,R12,12(R13)
BALR R12,0
USING BASEPT,R12
BASEPT DS 0H
这段代码将把调用程序的寄存器内容保存在调用程序提供的寄存器保护区域中(或操作
系统的保护区域中),并将R12建立为基址寄存器。在该子程序内部,R12和R13都不能再改
变。该程序应用如下指令退出。
LM R14,R12,12(R13)
BR R14
这将恢复调用程序原先的寄存器内容并将控制返回给调用程序。
程序7-1未调用任何外部子程序,此程序解释了对调用程序(或对操作系统)的恰当连
接。

***********************************************************************************************
* 本程序从输入记录读数据,每个记录包含两个数字。它们的和被打印出来。
***********************************************************************************************
*
SUMUP CSECT
STM R14,R12,12(R13) 保存调用程序的寄存器
BALR R12,0
USING BASEPT,R12
BASEPT DS 0H
*
XPRINT HEADING,28 打印页头
*
XREAD CARD,80 读第一个记录
*
CHECKEOF BC B’0100’,EXIT 数据结束则跳转到 EXIT
*
XDECI R2,CARD 假设两个数字都有效
XDECI R3,0(R1)

- 129 -
*
AR R2,R3 计算总和
*
XDECO R2,OUTPUT 以可打印形式放到打印行里
*
XPRNT CRG,13 打印总和
*
XREAD CARD,80 尝试读下一记录
B CHECKEOF 继续循环
*
EXIT LM R14,R12,12(R13) 恢复调用程序的寄存器
BR R14 返回调用程序
*
CARD DS CL80 数据输入区域
*
CRG DC C’ ‘ 单个空格打印控制符
OUTPUT DS CL12 在此输出总和
*
HEADING DC C’1THIS IS THE OUTOUT OF SUMUP’
R0 EQU 0
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6
R7 EQU 7
R8 EQU 8
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END SUMUP

程序 7-1

上例中主程序没有调用外部子程序。当主程序或子程序调用外部子程序时情况变得更为
复杂。为了便于解释,假定SUBRTN1调用SUBRTN2,SUBRTN2又要调用SUBRTN3,则
SUBRTN2应该做好如下的工作:
z 建立一个寄存器保护区域供SUBRTN3用;
z 将这一寄存器保护区域双重连至SUBRTN1提供的寄存器保护区域;

- 130 -
z 将这一寄存器保护区域的地址放入R13,然后调用SUBRTN3;
这些工作可以通过一系列类似于如下序列的指令来完成:
LA R14,SAVEAREA
ST R14,8(R13)
ST R13,4(R14)
LR R13,R14
第一条ST指令将SUBRTN1的寄存器保护区域的前向指针填充为SUBRTN2寄存器保护
区域的地址,第二条ST指令将SUBRTN2的寄存器保护区域的后向指针填充为SUBRTN1寄存
器保护区域的地址。最后LR指令将SUBRTN2寄存器保护区域的地址放入R13。
程序7-2解释了这些约定的用法。这个例子中,MAIN调用了两个子程序READTAB和
SORTTAB。请仔细研究本程序,观察它于程序7-1的不同之处。

TITLE ‘EXAMPLE OF A PROGRAM CALLING AN EXTERNAL SUBROUTINE’


*************************************************************************************************
* 此程序调用子程序 READTAB 来读入一个整数的表格,XDUMP 此表格,调用
* SORTTAB 来实现对表格排序,并且 XDUMP 排序后的表格,程序逻辑如下:
*
* 步骤 1:调用 READTAB 读入表格的数据
* 步骤 2:输出此表格
* 步骤 3:调用 SORTTAB 对表格排序
* 步骤 4:输出排序后的表格
* 步骤 5:退出
************************************************************************************************
*
MAIN CSECT
STM R14,R12,12(R13) 保存调用程序的寄存器
BALR R12,0 建立基址寄存器
USING BASEPT,R12
BASEPT DS 0H
LA R14,SAVEAERA 现在连接上保存区域
ST R14,8(R13)
ST R13,4(R14)
LR R13,R14 使 R13 指向新的保存区域
*
***<步骤 1>:调用 READTAB 读入表格的整数
*
LA R1,=A(TABLE,NUMENT)
L R15,=V(READTAB)
BALR R14,R15 调用 READTAB
*
***<步骤 2>:输出未排序的表格
*
XDUMP TABLE,200
*

- 131 -
***<步骤 3>:调用 SORTTAB 对表格排序
*
LA R1,=A(TABLE,NUMENT)
L R15,=V(SORTTAB)
BALR R14,R15
*
***步骤 4:打印排序后的表格
*
XDUMP TABLE,200
*
***步骤 5:退出
*
L R13,4(R13) R13 现在指向旧的保存区域
LM R14,R12,12(R13) 恢复调用程序的寄存器现场
BR R14 返回调用程序
*
LTORG
*
SAVEAREA DS 18F 寄存器保存区域
TABLE DS 50F 表格
NUMENT DS F 设为表格表项数目
*
*
**********************************************************************************************
* READTAB 子程序
* 本程序是一个读入一个表格的数据的外部子程序,传给他的参数必须有:
* 表格的地址
* 表格中的数的个数
* READTAB 的逻辑如下:
* 步骤 1:设一个指针指向表格的第一个条目
* 步骤 2:读第一个记录
* 步骤 3:如果检测到文件尾,跳转到步骤 6
* 步骤 4:把数据写入表格
* 步骤 5:读下一个记录
* 步骤 6:计算所有数据的总和,保存,并返回调用程序
***********************************************************************************************
*
READTAB CSECT
STM R14,R12,12(R13) 保存调用程序的寄存器现场
BALR R12,0 建立基址寄存器
USING READTAB,R12
READBASE DS 0H
*
LM R2,R3,0(R1) R2 <- A(TABLE)

- 132 -
* R3 <- A(NUMBEROF ENTRIES)
*
***<步骤 1>:设一个指针指向表格的第一个条目
*
LR R4,R2 R4 指向下一个打开的条目
*
***<步骤 2>:读第一个记录
*
XREAD CARD,80
*
***<步骤 3>:如果检测到文件尾,跳转到步骤 6
*
TESTEND BC B’0100’,EXIT
*
***<步骤 4>:把数据写入表格的下一个条目
*
XDECI R5,CARD
ST R5,0(R4) 保存表格的数据
LA R4,4(R4) 指向下一个条目
*
***<步骤 5>:读下一个数据并跳回步骤 3
*
XREAD CARD,80
B TESTEND
*
***<步骤 6>:保存表格的数据并返回
*
EXIT SR R4,R2
SRA R4,2 R4 存放条目的号码
ST R4,0(R3) 返回结果给调用程序
*
LM R14,R12,12(R13) 恢复调用程序的寄存器现场
BR R14 返回调用程序
*
LTORG
*
CARD DS CL80 数据输入空间
*
****************************************************************************************************
* SORTTAB 子程序
* 本程序可以被调对一个表格的数据递增排序,传给它的参数必须有:
* 被排序的表格的地址
* 表格中数的个数
* 为了理解算法的运作,把表格想像成由已排序和未排序两部分组成。已排序部分

- 133 -
* 的数据以升序排列。开始时只有最左边的数字是已排序部分。现在把未排序部
* 分的第一个数字拿出来,已排序部分移位跳过任何一个比它大的数字,移到为
* 已排序列表而设的空间里。然后把未排序的那个数据放到列表里。因此,我们
* 每次从未排序部分移动一个数据到已排序列表里。不断这样直到所有未排序数
* 据都被移走为止。此时排序完成。程序逻辑如下:
*
* 步骤 1:设一个指针指向表格的第二个数据(第一个未排序元素)。取得未
* 排序的段的数据总数
* 步骤 2:如果未排序段为空,则跳转到步骤 8
* 步骤 3:获得未排序段的第一个数据
* 步骤 4:如果它的左边没有数据或者它左边的数据比它小,跳转到步骤 6
* 步骤 5:从已排序段移动一个数据到右面,把指针递减使他指向下一个要
* 检查的数据。跳回步骤 4。
* 步骤 6:把当前取出的未排序元素放到已排序列表的正确位置。
* 步骤 7:把指针移到第一个未排序数据的右边
* 步骤 8:返回调用程序
*****************************************************************************************************
SORTTAB CSECT
STM R14,R12,12(R13) 保存调用程序寄存器
BALR R12,0
USING SORTBASE,R12
SORTBASE DS 0H
*
***<步骤 1>:取得指向第一个未排序数据的指针并计算未排序数据的总数
*
LM R2,R3,0(R1) R2<- A(TABLE)
* R3<- A(表格中数的个数)
LA R4,4(R2) R4<-A(第一个未排序数据)
L R5,0(R3)
BCTR R5,0 R5<-未排序数据个数
*
***<步骤 2>:如果未排序段为空,则跳转到步骤 8
*
CHECKEND S R5,=F’1’
BM SORTEXIT 如果没有未排序数据就跳转
*
***<步骤 3>:获得未排序段的第一个数据
*
L R6,0(R4) R6<-第一个未排序数据
LR R7,R4
S R7,=F’4’ R7<-A(下一个查询的数据)
*
***<步骤 4>:如果没有应该移位的数据,跳转到步骤 6
*

- 134 -
CHECK1 CR R7,R2
BL GOTSPOT 如果没有数据,跳转
C R6,0(R7)
BNL GOTSPOT 如果数据没有被移动则跳转
*
***<步骤 5>:跳过一个数据并跳回步骤 4
*
MVC 4(4,R7),0(R7)
S R7,=F’4’
B CHECK1
*
***<步骤 6>:把数据放到已排序列表的正确位置。
*
GOTSPOT ST R6,4(R7)
*
***<步骤 7>:把指针增一,指向未排序数据段
*
LA R4,4(R4)
B CHECKEND
*
***<步骤 8>:返回调用程序
*
SORTEXIT LM R14,R12,12(R13) 恢复调用程序寄存器
BR R14 退出
*
LTORG
R0 EQU 0
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6
R7 EQU 7
R8 EQU 8
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END MAIN

- 135 -
程序 7-2

3.小结
至此,我们已经介绍了编写和调用外部子程序的方法。现在我们小结一下。请读者牢记
并遵守IBM的寄存器连接约定:
z R13应总是包含一个寄存器保护区域的地址,因此,不应用于常规计算目的。
z R1,R14和R15不应用作基址寄存器,而且由于他们经常用于子程序调用,因此不
应装入一些经常使用的值。
尽管寄存器连接约定似乎很复杂,但读者只需要牢牢记住并遵守以下约定就可以了。约
定可以分为两个相当简单的情形:
⑴.一个不调用外部子程序的程序应以类似于如下的一段代码开始:
ROUTINE CSECT
STM R14,R12,12(R13)
BALR R12,0
USING BASEPT,R12
BASEPT DS 0H
这段代码将把寄存器的内容保存起来,并将R12建立为基址寄存器(记住BALR指令
用R0作为第2个操作数仅把下一第指令的地址装入连接寄存器)。在程序内部,基址寄存器和
R13均不得改变。程序应以如下指令返回调用程序:
LM R14,R12,12(R13)
BR R14
⑵.可能访问一个或多个外部子程序的程序应以类似于如下的指令序列开始:
ROUTINE CSECT
STM R14,R12,12(R13)
BALR R12,0
USING BASEPT,R12
BASEPT LA R14,SAVEAREA
ST R14,8(R13)
ST R13,4(R14)
LR R13,R14
在这种情况下,程序应用如下指令退出:
L R13,4(R13)
LM R14,R12,12(R13)
BR R14
在总结本节时,应注意使用R13同时作为保护区域指针和基址寄存器是可能的,这具有
留出一个寄存器用作计算用的优点。如下代码可以达到这一目的:
ROUTINE CSECT
STM R14,R12,12(R13)
BAL R14,80(R15)
SAVE DS 18F
ST R13,4(R14)
ST R14,8(R13)
LR R13,R14
USING SAVE,R13

- 136 -
这节代码应仔细研究。其实理解这段代码如何工作并不困难,如果理解指令:
BAL R14,80(R15)
将SAVE的地址装入R14,并使分支绕过SAVE到指令:
ST R13,4(R14)
必须记住,根据调用约定,R15包含进入程序时的第一条指令的地址。

第三节 与高级语言之间的连接

可以编写被汇编和保存在目标库中的子程序的机制是非常重要的。这一机制允许创建强
有力的服务程序,而这些程序允许每个汇编程序调用。幸运的是,汇编程序也可以很容易地
被许多高级语言调用。对于S/390而言,在FORTRAN、COBOL和汇编程序间的连接是非常
容易的。所有这些接口的细节可以从IBM手册中找到(通常在高级语言程序员指南中讨论同
其它语言的程序接口)。本节中,我们将介绍汇编程序如何与FORTRAN和COBOL程序接口。

1.从高级语言调用汇编子程序
要从COBOL和FORTRAN程序访问一个汇编子程序,可以使用CALL语句。因此,从
FORTRAN语言中要访问汇编子程序SORTTAB可以使用:
CALL SORTTAB(V,N)
其 中 V 假 定 为 全 字 整 数 向 量 而 N 为 单 个 整 数 。 这 要 求 V 和 N 定 义 为 INTEGER*4 。
FORTRAN数据类型的精确存贮格式在FORTRAN程序员手册中给出。
要从COBOL中访问SORTTAB可以使用:
CALL 'SORTTAB' USING V N
其中V和N在程序的数据部分应该声明为:
01 V.
05 TABEL PIC S9(5)
USAGE IS COMP OCCURS 50 TIMES.
01 N PIC 9(5)9 USAGE IS COMP.
要从FORTRAN或COBOL程序中以此方式调用SORTTAB,必须对SORTTAB加以修改以
遵守这些语言所需的连接约定,这些规定是:
①.R15用作返回代码。通常,它应被子子程序设置为0。我们总是推荐在高级语言调
用的子子程序中将R15设置为0。
②.在退出子程序前,寄存器保护区域的第十三字节(12(R13))必须设置为X'FF'。
根据以上规定,汇编子程序SORTTAB必须修改为使用如下代码以退出:
EXIT LM R14,R12,12(R13)
MVI 12(R13),X'FF'
SR R15,R15
BR R14
这些细微的变化就是编写可以从COBOL或FORTRAN中调用汇编子程序所需要要做的。

2.从汇编语言调用高级语言子程序

- 137 -
尽管从COBOL或FORTRAN访问汇编子程序是普通的,从汇编语言中访问高级语言程序
则很少见。然而,在某些场合下还是可能会需要的。幸运的是,这样的调用也遵循上面讨论
过的连接约定。
例如,如果要调用一名字为ILBOSTPO的COBOL子程序,该子程序不需要任何入口参
数,则可以使用下面的语句序列:
L R15,=V(ILBOSTPO)
BALR R14,R15
若需要传递入口参数,例如EXTNUM和INTNUM,可以使用下面的语句序列:
LA R1,=A(EXTNUM,INTNUM)
L R15,=V(FLETOI)
BALR R14,R15
已经进行的讨论相当简单。感兴趣的读者可以在相应的语言参考和程序员指南中找到汇
编语言和高级语言间连接的完整讨论。

第四节 虚拟段的使用

在S/390汇编语言中,除了控制段CSECT之外,还有一种特殊的段称为虚拟段(DSECT)。
虚拟段的定义如下;
label DSECT

虚拟段的范围由出现定义开始直到遇到CSECT、另一个DSECT或END语句时结束。虚
拟段中的语句说明了一种数据格式,但是这些语句不会产生任何目标代码。例如,如下
DSECT描述一个三个字的内存区域:
PARMLIST DSECT
TABLADDR DS A 表格地址
KEYADDR DS A 关键字地址
NUMENT DS F 表项项数
在程序中,我们可以把DSECT描述的格式与存贮器的一特定区域关联,从而可以方便
地访问该区域。DSECT语句的标号可用于说明产生目标代码的汇编语句区域中的地址。因
此,在汇编语句使用虚拟段中的标号引用虚拟段中字段以前,必须说明包含该虚拟段地址的
寄存器。如果在执行时指定的寄存器不包含正确的地址,会产生不可预料的错误。如下形式
的语句:
USING DSECT, r

向汇编程序指明在寄存器r地址处的区域同第一个操作数指定的虚拟段的格式一致。汇
编程序利用得自于USING语句的信息,将虚拟段中标号说明的地址转换为相对于寄存器 r
的地址偏移,作为一个例子,考虑如下代码段:
USING PARMLIST,R1
L R2,NUMENT
L R5,KEYADDR
因为USING语句通知了汇编程序在程序的执行期间R1将包含内存中一个区域的地址,
该区域同虚拟段PARMLIST说明的格式一致,因此要装入R2的字的位置在R1地址偏移的第8

- 138 -
个字节处。因此,第一条装载指令的编码为5820 1008。类似地,对KEYADDR的引用转换为
R1地址偏移的第4个字节。前述例子产生的的实际代码显示在程序7-3汇编清单中。

CSECT EXAMPLE – SUBROUTINE TO SEARCH TABLE


———————————————————————————————————
LCC Object code addr1 addr2 stmt source statment
000000 34 PARMLIST DSECT
000000 35 * THIS DSECT GIVES THE FORMAT
000004 36 TABLADDR DS A
000008 37 KEYADDR DS A
000000 38 NUMENT DS F
40 SEARCH CSECT
42 ***<STEP1> SAVE REGS
000000 90EC 000C 0000C 43 STM R14,R12,12(R13)
000004 05C0 44 BALR R12,R0
000006 45 USING *, R12
000006 48 USING PARMLIST,R1
000006 5820 1008 00008 49 L R2,NUMENT
00000A 5850 1004 00004 50 L R5,KEYADDR
00000E 5830 1000 00000 51 L R3,TABLADDR

程序7-3

通过使用DSECT,至少可以得到两个好处:
⑴.程序的可读性得到大大改进。例如
L R2,NUMENT
远比如下指令清楚:
L R2,8(R1)
⑵.采用DSECT的程序比只直接采用直接地址的程序更容易修改。
为解释第2个好处,假定要调用SEARCH的程序已经以不同顺序定义参数。在包含
PARMLIST的虚拟段的子程序中,这种情形可以简单地通过重新编排虚拟段中语句的顺序进
行改正。然后,当SEARCH子程序再次汇编时,所有的地址将被转换为正确的值。如果不是
这样而采用直接地址,在重新汇编程序之前,必须改变引用参数表字段的每条语句。
在前面的例子中,DSECT用于单个区域的格式。然而程序执行期间,一个DSECT可以
用于描述存贮器中不止一个的区域。程序7-4中的SEARCH完整清单对此情形进行了解释(程
序中有些指令的解释放到下一章)。在这种情况下,每个被引用的区域必须同描述于虚拟段
中的格式一致,说明于USING语句中的寄存器必须在实际访问区域字段之前加以调整,以
包含指定区域的地址。在SEARCH中,虚拟段TABENTRY用于说明表中每个项的格式,R3
用作指向表项的指针。因此,通过改变R3(看作TABENTRY的基址寄存器),虚拟段中的标号
可用于说明表中的任何特定表项。
在使用虚拟段方面还有一些技巧:在程序中的不同部分可以对虚拟段使用不同基址寄存
器。因此,在一段代码中,语句
USING AREA,R5
可用于指明R5为AREA的基址寄存器,而语句

- 139 -
USING AREA,R4
可以出现在同一程序的另一段代码中。为了使汇编程序清楚哪个寄存器用于转换隐式地
址到显式地址,在发出另一个USING语句前,前面的USING语句的作用必须加以取消。这
通过使用DROP语句完成,该语句具有如下格式:
DROP r1, r2, … , rn

这一语句简单地用于通知汇编程序寄存器r1, r2, …, rn在计算显式地址时不再用作基址


寄存器。
作为一个解释,考虑如下代码段
AREA DSECT
FIELD1 DS CL10
FIELD2 DS H
FIELD3 DS F
MAIN CSECT
……
USING AREA,R5
……
USING AREA,R4
L R1,FIELD3
这里,汇编程序将不清楚地是FIELD3将转换为12(R5)或12(R4)。然而,如果语句
DROP R5
插入到第2个USING语句之前,除非以后被另外一个USING语句说明,R5将在其后的语
句中不用作AREA的基址寄存器。因此,FIELD3就能毫无混淆地转换为12(R4)。在一段代码
引用特定虚拟段后将其基址寄存器DROP总是一个好的习惯。当隐式地址转换为显式地址
时,这将保证希望的基址寄存器被汇编程序选中。

***********************************************************************************************
* 此子程序被调用作搜索一个如下格式的表格:
*
* KEY+0(6) 待搜索表格的关键字
* PROD# + 6(5) 产品编号
* PRICE +11(4) 以压缩十进制数形式存储的价格,以 XXXX.XX
* 的形式存储
* STOCK +15(4) 库存货品的数量(压缩十进制数)
*
* 子程序的输入是一个参数表,格式如下
*
* TABLE +0(4) 待搜索表格的地址(格式如上)
* KEY+4(4) 供搜索关键字的地址
* # +8(4) 表格条目的总数
*
* 这里的 N(M)表示一个相对位移为 N 字节的 M 字节的区域
* 子程序的逻辑如下
*

- 140 -
* 步骤 1:保存调用程序的寄存器现场,并且建立基址寄存器;
* 步骤 2:搜索表格寻找条目,如果找不到,转向步骤 4,否则,转向步骤 3;
* 步骤 3:打印一条错误信息,找不到条目。跳转到步骤 5;
* 步骤 4:打印匹配的条目;
* 步骤 5:退出。
***********************************************************************************************
*
PARMLIST DSECT
*此数据段给出输入参数的格式
TABLADDR DS A 待搜索表格的地址
KEYADDR DS A 关键字的地址
NUMENT DS F 表格条目总数
*
SEARCH CSECT
*
***<步骤 1>:保存调用程序的寄存器现场,并且建立基址寄存器
*
SIM R14,R12,12(R13) 保存调用程序寄存器现场
BALR R12,R0 设置基址寄存器
USING *,R12
*
***<步骤 2>:扫描表格寻找特定条目
*
USING PARMLIST,R1 R1 包含输入参数的地址
L R2,NUMENT 装载表格的条目
L R5,KEYADDR 装载关键字的地址
L R3,TABLADDR 装载表格地址
USING TABENTRY,R3
SRCHLP CLC TABKEY ,0(R5) 把条目中的关键字与要搜索的关键
* 字比较
BE GOTENTRY
LA R3,TANXENT 装载下一条目的地址
BCT R2,SRCHLP 如果还有条目未检查则跳转
*
***<步骤 3>:打印错误信息
*
MVC ERRKEY,0(R5) 把关键字送到错误信息
XPRNT ERRLINE,35 打印错误信息
B LEAVE
*
***<步骤 4>:打印匹配的条目
*
GOTENTRY MVC PRNTKEY,TABKEY 把关键字送到打印行
MVC PRNTPROD,TABPROD# 把产品号送到打印行

- 141 -
MVC PRNTPRIC,=X’4020202020214B2020’
ED PRNTPRIC,TABPRICE 编辑价格
MVC PRNTSTCK,=X’40206B2020206B202120’
ED PRNTSTCK,TABSTOCK 编辑对应货品库存数量
XPRNT PRNTLINE,37
*
***<步骤 5>:退出
*
LEAVE LM R14,R12,12(R13)
BR R14
LTORG
=X’40206B2020203B202120’
=X’4020202020214B2020’
ERRLINE DC C’0’ 传输控制
ERRKEY DS CL6 找不到的关键字的值
DC C ‘ – ENTRY IS NOT FOUND IN THE TABLE‘
PRNTLINE DC C’0’ 传输控制
DS CL6 匹配的关键字
DC CL2’ ‘
PRNTPROD DS CL5 匹配的条目的产品编号
DC CL2’ ‘
PRNTPRIC DS CL9 匹配的条目的价格
DC CL2’ ‘
PRNTSTCK DS CL10 库存数量
R0 EQU 0
R1 EQU 1
R2 EQU 2
R3 EQU 3
R5 EQU 5
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
TABENTRY DSECT
* 此数据段给出表格条目的格式
TABKEY DS CL6 关键字
TABPROD# DS CL5 产品编号
TABPRICE DS PL4 XXXX.XX 形式的价格
TABSTOCK DS PL4 库存的数量
TABXTENT DS 0X 下一条目地址
END

程序7-4

- 142 -
习题七

1.在本习题中,你将写一个程序来读一个字符串和一个短的模式串。程序调用一个子程序
INDEX,它有四个参数。
a. 短模式串的地址
b. 包含了模式串长度的字的地址
c. 要查找串的地址和
d. 包含要查找串长度的字的地址。
子程序将R0设置为模式串第一次出现的地址(当串中没有模式串时为0)。例如,INDEX
用下面参数调用:
ABGTAXDRK*&CGTSOOHT 作为串
XDRK 作为模式串
那么R0将被设为串中第六个字节的地址。
有一个相当快的算法从一个字符串中查找一个子串,称为Boyer-Moore串搜索算法,它
大致按以下步骤工作:
z 首先,由于模式串为4个字符长,检查串的第四个字符,这个字符是T,既然T没有
出现在模式中,可以知道在第五个字符之前,模式是不可能匹配的。
z 从第五个字符或以后开始寻找匹配,我们再一次从可能出现匹配的地点往后看四个
字符,即我们看到R出现在串中,由于它出现在模式串中,但不是最后一个字符,我们
向前移动一个距离,即模式串的最后距R的距离(这儿为1个字符)。
z 现在我们从可能与模式串匹配的地方看第四个字符,我们找到了K,由于K是模式
串的最后一个字符,我们向前做一个完全匹配,看我们是否实际找到了模式串的匹配。
一个更精确的算法形式是用下面的伪码形式给出的。
INDEX:(Proc(STRING,SLEN,PATTERN,PLEN)
Create a 256 character table TAB, in which every
character is initialized to PLEN.
I ← 1
Do while (I<=PLEN)
C ← Ith character of the PATTERN
Set the Cth entry of TAB (i.e., TAB[C]) to PLEN-I
I←I+1
Enddo

MATCH←0
I ← 1
Do while ((I<=(SLEN-PLEN)) and (MATCH = 0)
C ← the (I+PLEN-1)th character of STRING
if (TAB[C] = 0)
if (PATTERN matches STING at Ith location)
MATCH ← I
else
I ← I+1

- 143 -
Endif
else
I ← I+TAB[C]
Endif
Enddo

return MATCH
Endproc
这个算法有些复杂而且这里有一个显著的初始化开销。然而,初始化的代价能被显著减
少,并且对相当长的串,在速度方面能获得比用直接方式令人振奋的结果。

2.写一个子程序NEXTWORD用来从一个串中寻找下一个“单词”,为了练习这一目的,可
以考虑任何直到下一个空格(或串尾)的字符作为一个单词,子程序列出的参数应有下列
格式。
z 字符串的地址(用X'00'限定串尾)
z 包含串中第一个非空白符指针(0,如果没有非空白符)的全字地址
z 包含串中单词后第一个字符(空格或X'00'限定字符)指针的全字地址。

3.写一个子程序计算在一行中字符和单词的个数,子程序应当接受下面的参数表:
z 字符串的地址(用X'00'限定串尾)
z 一个包含了设置行中字符个数的全字的地址(不包括X'00')
z 一个包含了设置行中单词个数的全字的地址。

4.写一个子程序从一个整数表中移去相同的整数,此子程序接受下列参数表:
z 整数表的地址
z 包含了表中表项个数的字的地址。

5.这个题目与练习4相同,除了这里你必须假设表中的值是按升序排列的。

6.SHELL排序算法是一个更为有效的排序算法,编一个子程序SHELL,采用如下参数表:
z 整数表的地址
z 包含了整数表中表项数目的字的地址。
假设表称为V,N包含了表V中表项个数,那么SHELL使用下面的算法给V中表项排序。
步骤 1.GAP←N/2,GAPDIS←GAP*4, ENDPT← V最后表项地址.
步骤 2.If GAP =0, goto 步骤 11. (表排序了.)
步骤 3.NEXTEL← V的地址 + GAPDIS. NEXTEL引用第(GAP+1)表项.
步骤 4.If NEXTEL> ENDPT, goto 步骤 10.
步骤 5.SORTEL ← NEXTEL -GAPDIS
步骤 6.If SORTEL<V, or SORTEL指的表项<=SORTEL +GAPDIS表项, goto步骤
9.
步骤 7.交换SORTEL和SORTEL +GAPDIS处的表项.
步骤 8.SORTEL ← SORTEL - GAPDIS. goto 步骤 6.
步骤 9.NEXTEL ← NEXTEL + 4. goto 步骤 4.
步骤 10.GAP ← GAP/2. GAPDIS ← GAP *4. goto 步骤 2.

- 144 -
步骤 11.退出.
若向量记号跟伪码一起使用,整个算法会更清晰,在这个例子中,算法如下:
SHELL: PROCEDURE(V,N)
GAP ← N/2
Do while (GAP>0)
NEXTEL ← GAP +1
Do while (NEXTEL<=N)
SORTEL ← NEXTEL -GAP
Do while (SORTEL >0 and V (SORTEL) >V (SORTEL=GAP))
Exchange V (SORTEL) and V (SORTEL +GAP)
SORTEL ← SORTEL - GAP
Enddo
NEXTEL ← NEXTEL +1
Enddo
GAP ← GAP/2
Enddo
Endproc
写程序读一表整数,打印表,调用SHELL对表排序,并打印排好序的表。

- 145 -
第八章 十进制运算

在本教材的第二和第三章中我们讨论过十进制数据和它们的定义形式,本章中我们将对
十进制指令和它们的应用做更详细的讨论。
十进制指令的提出可以为以下目的提供方便:
⑴.完成熟悉的十进制系统表示的整数算术操作;
⑵.在打印前编辑成可读形式的数据。
S/390中的十进制数有两种格式:压缩十进制和非压缩十进制。两种不同格式的十进制
格都可以表示十进制数,需要哪种格式由所用的特定指令来指明。通常,压缩格式用于算术
操作,非压缩格式用于输入/输出操作。
除了在十进制数字上进行操作的机制外,十进制指令集较之二进制指令集而言,还提供
了另外一些优点和方便。所有的十进制指令均为SS格式(存储器-存储器),且允许操作数为
变长字段。因为1至31个数字的十进制数可以以紧凑方式表示,这较二进制算术而言提供了
额外的精度。另外由于算术操作在存贮器中进行,完成操作通常只需要一条指令。例如,它
只需一条指令就可以完成两个十进制字段的加法,如果是二进制加法的话,我们将需要L、
A和ST三条来共同完成。此外,有一个极方便的办法来完成十进制数的近似取值,这一功能
绝对是某些应用所需要的。

第一节 压缩和解压指令

我们已经知道,压缩十进制和非压缩十进制之间是有一定关系的,有时我们需要在这两
种格式间做转换。S/390中提供了这样的转换指令。在非压缩和压缩十进制数表示间相互转
换的指令为带有两个长度指示的SS指令,这一类型指令的通用格式称为十进制SS格式,格
式如下所示:
标号 助记符 D1(L1,B1),D2(L2,B2)

如果使用了隐式地址,应用如下的格式:
标号 助记符 field1(L1), field2(L2)

它们所产生的机器代码形式为:

hOhOhL1hL2 hB1hD1hD1hD1 hB2hD2hD2hD2


其中: hOhO操作码
hL1是第1个操作数的长度编码
hL2是第2个操作数的长度编码
hB1hD1hD1hD1第一操作数的地址
hB2hD2hD2hD2第二操作数的地址
注意: hL1、hL2每个长度编码占4个二进制位。这意味着15是最大的可能长度编码而16为
最大的操作数的合法长度。因而,非压缩格式十进制可以表示1到16位十进制数字,而压缩
格式十进制可以表示到1到32位十进制数字。另一是值得注意的是:如果未指定长度的隐式
地址用做任何一个操作数,该符号长度属性将被用作假定的长度。

- 146 -
1.压缩指令PACK
压缩指令PACK的格式为:
PACK D1(L1,B1),D2(L2,B2) (压缩指令)

要转换的非压缩十进制数地址由第2个操作数说明,结果存放在其地址由第1操作数说明
的字段中。该语句的执行具有如下效果:
⑴.第2个操作数的最右边的字节放在第一个操作数的最右边字节,并将该字节的高四
位二进制与低四位二进制交换;
⑵.第2个操作数的其余数值数字存入到第一个操作数,按自右至左的顺序进行。如果
第2个操作数中的数字多于第一个操作数可以容纳的数字,最左边的数字被忽略;如果第2
个操作数的数字个数少于填满第一个操作数所需要的数字。第一个操作数剩余的数字位置被
填为0。
本指令的操作码为D6,指令的执行不影响条件码CC。
作为一个例子,假定F1和F2如下所示:
F1: 00 00 00 00
F2: F9 F8 F7 F6 F5 F4 F3
则指令:
PACK F1(4),F2(7)
执行后F1被设置为98 76 54 3F。图8-1解释了这过程:

F2 F9 F8 F7 F6 F5 F4 F3

F1 98 76 54 3F

图8-1 压缩过程示意图

同样地,指令:
PACK F1(3),F2(7)
将F1设置为76 54 3F 00;指令:
PACK F1(4),F2(5)
将F1设置为00 98 76 5F。
注意PACK指令的执行并不要求第2个操作数一定为一个合法的非压缩十进制数。例如,
如果F1的值如上例,而F2的值为:
C1C2C3C4D8F101
则指令PACK F1(4),F2(7)的执行将F1设置为12 34 81 10。
记住PACK指令的执行不检查要压缩的数据这一点是很重要的。正因如此,任何字节的
十六进制数字可以很容易地通过压缩该字节到其自身而实现交换。例如,指令:
PACK BYTE(1),BYTE(1)

2.解压指令UNPK
解压指令UNPK的格式为:

- 147 -
UNPK D1(L1,B1),D2(L2,B2) (解压指令)

一个UNPK指令的执行将产生如下动作:
⑴.第2个操作数最右边的字节放在第一个操作数最右边的字节,并将该字节的高四位
二进制与低四位二进制交换;
⑵.从第2个操作数的第二个字节开始,每4位十六进制数字提出来,与一个十六进制数
F合并成为一个字节存放到操作数1中。如果在第2个操作数中有多的数值数字,则第2个操
作数中最左边剩下的数值数字被忽略。如果第2个操作数的数值数字少了,则第一个操作数
剩下的左边的字节被置为F0。
本指令的操作码为F3,指令的执行不影响条件码CC。
例如,假定F1和F2如下所示:
F1: 00 00 00 00 00
F2: 12 34 5C
则,指令:
UNPK F1(5),F2(3)
将F1设置为F1F2F3F4C5。类似地,如下指令:
UNPK F1(1),F2(3)
的执行将F1设置为C500000000。以下指令
UNPK F1(5),F2(2)
将F1设置为F0F0F1F243。
PACK和UNPK指令一般情况下配对使用,用于将数值数据以十进制形式进行算术操作
后以再字符格式打印和显示。这其中,PACK指令用于将非压缩的十进制数据转化为压缩格
式(在这种格式下,十进制指令用于完成算术运算);UNPK指令则用于将运算结果转换为
字符格式(类似于非压缩格式)以便打印输出,这一过程可以用图8-2来表示。

字字字 PACK 紧紧紧


式式式 式式式

完完完
术术完

字字紧 UNPK 产产紧紧


式格格 紧式格格

注:图中的紧凑格式即压缩格式
图8-2 PACK/UNPK指令使用示例图

然而我们必须加以小心的是,当解压一个数据项时,为了保证产生的符号是可打印字符,
必须通过使用OI指令来设置符号。
后面我们将讨论,PACK/UNPK指令也可以同转换压缩十进制数到二进制数(或反之)的
指令一起工作,前面已经介绍过的ASSIST宏指令XDECI和XDECO用于完成上述操作。

- 148 -
第二节 十进制加减运算

在本节中,我们将讨论一些基本的可用于压缩十进制数算术操作,包括加法和减法。在
我们进行这些讨论前,应注意使用压缩格式而非二进制数的优点。较之于二进制数,压缩十
进制数的主要优点在于它表示的数值对人而言更可读。因此,如果数值由压缩十进制表示的
话,程序员在进行扫描阅读DUMP报告的时候,可以很容易地看出操作所涉及的数据是什么。
例如:十进制数+123的压缩形式
0123C
比起对应的二进制形式的意思要明确得多:
007B
由此可见,在调试程序中通过使用压缩十进制数获得的方便是明显的。使用压缩十进制
数算术指令获得的另一个优点是,没有必要将压缩十进制数转换为二进制然后再转换回来。
使用压缩十进制数的也有不足之处:完成十进制算术操作的时间远大于二进制指令的时
间。因此,在决定采用何种格式之前,程序员必须仔细权衡使用十进制压缩数据的缺点与优
点。通常,在编写循环和经常执行的代码段时应避免使用压缩十进制数。最后,应注意在同
一程序里使用两种类型的算术运算指令是很少见的。
加、减、清零加十进制、比较压缩数据的指令分别为AP、SP、ZAP和CP指令。当这些
指令使用后,可能会碰到我们先前没有讨论的数据异常错误。当一个并非十进制压缩格式的
字段被说明为这些指令的操作数时,就可能会产生这样的错误。其它数据异常的原因将在讨
论单个指令时加以指示。
随这些指令的使用,可能会发生先前未讨论的的另一个错误:十进制溢出,这一错误类
似A和S指令定点溢出错误的情形。第二章中我们介绍过在PSW中有一位确定溢出是否导致
程序的终止的控制位,这里,正如二进制定点溢出错误一样,SPM指令可用于将该位(位37)
设置为on或off。完成这一工作的技术将在第9章讨论。

1.十进制加法指令AP
十进制加法指令AP的格式为:
AP D1(L1,B1),D2(L2,B2) (十进制加法)
功能:(D1+(B1) )←(D1+(B1) )+(D2+(B2))

指令的执行产生D1(L1,B1)和D2(L2,B2)指定的两个数的和,该和替换第一个操作数。如
果第一个操作数的存贮不足于容纳结果的所有非零数字,就会产生溢出。条件码设置如下:
CC 含义
0 结果为0
1 结果<0
2 结果>0
3 产生溢出
注意,如果第2个操作数的长度大于第一个操作数的长度,不一定产生溢出。仅当第一
个操作数的长度小于存放结果需要的长度时,才会产生溢出。
本指令的操作码为FA。

- 149 -
2.十进制减法指令SP
十进制减法指令SP的格式为:
SP D1(L1,B1),D2(L2,B2) (十进制减法)
功能: (D1+(B1) )←(D1+(B1) )-(D2+(B2)

指令的执行产生D1(L1,B1)和D2(L2,B2)指定的两个数的差,该差替换第一个操作数。本
指令的操作码为FB,指令执行影响条件码CC,条件码的设置如同AP指令一样。
例如指令:
SP FLD1,=P'4'
将从FLD1的内容中减去4,结果存入FLD1。

3.清零及加十进制指令ZAP:
清零及加十进制指令ZAP格式如下:
ZAP D1(L1,B1),D2(L2,B2) (清零及加十进制)
功能: (D1+(B1) )←(D2+(B2)

指令的执行将把第一个操作数设置为0,然后第二个操作数加第一个操作数并替换第一
个操作数的内容。因此,执行ZAP指令的实际结果是将第2个操作移入第一个操作数占据的
位置。本指令的操作码为F8。指令的执行影响条件码CC,条件码设置同AP和SP一样。数据
异常仅在第2个操作数不是一个合法的压缩十进制数时才会产生。ZAP指令常用于下述两个
目的之一:
⑴.将一个字段设置为0,例如如下指令将F1设置为全0(F1共n个字节)。
ZAP F1,=P'0'
⑵.将一个压缩十进制数从第一个字段移动到第二个字段。注意条件码的设置可用来
确定第二个操作数为正、负或零。
如下例子将有助于澄清AP、SP以ZAP指令的用法。假定F1、F2和F3具有给定值:
F1 01128C (+1128)
F2 001D (-1)
F3 00054F (+54)
如下指令的执行将产生如下结果:
指令 结果 条件码
AP F1,F2 01127C (F1中) 2
AP F3,F2 00053C (F3中) 2
AP F3,=P'-100' 00046D (F3中) 1
AP F2,=P'-999' 000D (F2中) 3
SP F1,=P'1' 01127C (F1中) 2
SP F2,F2 000C (F2中) 0
SP F2,F3 055D (F2中) 1
SP F2,P'999' 000D (F2中) 3
ZAP F1,=P'0' 00000C (F1中) 0
ZAP F3,F2 000010 (F3中) 1
ZAP F2,F1 128C (F2中) 3

4.比较十进制指令CP

- 150 -
比较十进制指令CP的格式为:
CP D1(L1,B1),D2(L2,B2) (十进制比较)
功能: (D1+(B1) )-(D2+(B2)

指令的执行对两个操作数进行比较,并用比较的结果影响条件码CC,条件码设置如下:
CC 含义
0 操作数相等
1 第一个操作数小
2 第一个操作数大
3 --
本指令的操作码为F9。

第三节 十进制乘除运算

在本节中,我们讨论十进制算术运算中的乘法和除法。十进制乘法和除法指令的用法有
些复杂,需要仔细斟酌

1.十进制乘法指令MP:
十进制乘法指令MP的格式如下:
MP D1(L1,B1),D2(L2,B2) (十进制乘法)
功能:(D1+(B1))←(D1+(B1) )×(D2+(B2)

指令的执行将形成两个操作数的积,结果替换第一个操作数,条件码被改变。如果L2
大于8或大于等于L1,就会产生数据规格异常错误;如果第一个操作数的前L2字节非全零,
就会产生数据异常错误;如果操作数不是合法的压缩十进制格式,也会产生数据异常错误。
本指令的操作码为FC。
为解释MP指令的使用,假定F1包含00015C,则指令:
MP F1,=PL1'5'
在F1中会留下00075C,然而指令:
MP F1,=PL2'5'
将产生一个数据异常错误,因为F1的前2个字节不是0000。如下指令的执行会产生一个数据
规格异常错误:
MP F1,=PL3'5'
因为F1只有三个字节长。

2.十进制除法指令DP
十进制除法指令DP的格式为:
DP D1(L1,B1),D2(L2,B2) (十进制除法)
功能: (D1+(B1) )←(D1+(B1) )/(D2+(B2))

第1个操作数除以第2个操作数产生商和余数。第一个操作数被该商和余数替换。商留在
D1(L3,B1)中,其中L3=L1-L2,而余数留在D3(L2,B1)中,其中D3=D1+(L1-L2),即右

- 151 -
部的余数正好和除数一样长,而商占据左边剩下的字节。如果L2大于8或L2大于等于L1,则
会产生一个数据规格异常错误;如果商无法存放在L1-L2字节中,就会产生除法异常错误;
如果任何一个操作数不是合法的压缩十进制格式,就会产生一个数据异常错误。本指令的操
作码为FD,指令的执行影响操作码。
为了解释,假定F1包含01500C,则指令:
DP F1,=PL2'298'
将在F1中留下5C010C。商5C在左部,余数010C在右部, 余数和除数一样长度为两个字节。
另一方面,指令:
DP F1,=PL3'298'
的执行将产生数据规格异常。试图执行:
DP F1,=PL2'15'
将产生十进制除法异常错误,因商100C不能存放于一个字节中。

第四节 输出编辑

编辑输出类指令包括两条指令:ED和EDMK。
编辑指令用于转换一个压缩十进制数或相邻顺序的压缩十进制数,以便以合适的格式打
印输出。它为抑制前导零、插入逗号及小数点、补接代数符号以及插入文本提供了机制。正
因为它执行如此多的功能,这类指令的执行算法非常复杂。
我们首先讨论ED指令。
ED指令的格式为:
ED D1(L,B1),D2(B2) (输出编辑)

其中: D1(L,B1) 指令执行后格式化结果存贮字段的地址和长度


D2(B2)指出一个或多个压缩十进制数的首地址
指令的操作码是DE。在讨论此指令的功能之前,必须先介绍一些术语。
1.第一个操作数称为模式。模式中的字符决定了编辑结果的格式。模式中的字符分
类如下:
a. 20(即X'20')称为数字选择符
b. 21称为有效数字开始符
c. 22称为域分隔符
d. 其它任何字符为消息字符
2.第2个操作数称为源字段,其中的十六进制数字称为源数字。源字段可包含一个或
多个压缩十进制数,源数字的被编辑的数字个数由模式的内容确定。
3.有一个称为有效指示器的开关。该指示器或为开或为关。这一开关的功能将在下
述讨论中变得清晰。
4.模式的第一个字符称为填充字符。当要抑制前导零或消息字符时,填充字符将替
换这些字符。
模式和源字段均自左至右地处理,对模式每次处理一个字符,而对源字段每次处理一个
十六进制数字。开始执行时,有效指示器状态为关,检查模式的第一个字符并置为填充字符,
如果该字符不是数字选择符或有效数字开始符,该字符保持不变,处理模式的下一个字符。
自此,执行过程如下:

- 152 -
1.如果模式中的字符为数字选择符,要检查源字段的数字。
a. 如果有效指示器状态为关并且
①.如果该数字为0,模式中的数字选择符替换为填充字符,另一方面
②.如果该数字为非零十进制数字,则该数字转换为非压缩十进制格式,该
格式结果替换模式中的字符。如果这情况出现,有效指示器的状态置为
开。
b. 如果有效指示器状态为开,该数字转为非压缩十进制格式并替换模式中的字符。
2.如果模式中的字符是一个有效数字开始符,除了该字符被替换后有效指示器状态
总是置为开以外,结果同数字选择符的情形完全相同。
3.如果模式中的字符是一个域分隔符,该字符被填充字符替换且有效指示器状态置
为开。
4.如果模式中的字符是一个消息字符,并且
a. 有效指示器状态为关,则该消息字符替换为填充字符;
b. 有效指示器状态为开,该消息字符保持不变。
然后选中模式的下一个字符,处理过程重复进行直到模式处理完毕,模式中的每个字符
及源字段中的每个十进制数字仅检查一次,结果替换模式字符串。
在这一讨论中,一直假定源字段的格式是正确的,源字段无效的压缩格式可能导致数据
异常错误。当然,指令的地址不正确可能产生保护和地址错误。条件码设置如下:
CC 含义
0 最后一个源字段为0
1 最后一个源字段<0
2 最后源字段>0
3 --
以下我们用一组例子来说明ED指令的功能和用法:

例1:假定F1包含40202020且F2包含123C,则指令:
ED F1(4),F2
将在F1中留下40F1F2F3(即同C' 123')。为了看清楚,仔细地执行一遍算法并注意以下:
1.因40(即空格)为模式的第一个字符,成为填充字符;
2.检查模式中的后继字符时,每个后继字符替换为相应源数字的字符形式(因后3个模
式字符为数字选择符)。
因此,如果F2三个数字的十进制数值要编辑到PLINE的第3到第5字节,可以使用如下代码:
MVC PLINE+1(4),=X'40202020'
ED PLINE+1(4),F2

例2:例1有一个问题,如F2包含0(即000C),因算法用填充字符替换前导零,将在PLINE
只放入空格。为了矫正这一问题,使用有效数字开始符如下:
MVC PLINE+1(4),=X'40202120'
ED PLINE+1(4),F2
除了F2包含0,其它任何情况这一模式产生的结果同模式40202020完全一样。然而,如F2正
好包含000C,程序将一个零而非所有空格。注意只会打印一个零,有效数字开始符不会打
印相应位置的零,而是其后的零被打印出。

- 153 -
例3:假定F2中的数要转换作X·XX的形式,在这种情况下,十进制小数点(X'4B')应插
入模式中:
MVC PLINE+1(5),=X'40204B2020'
ED PLINE+1(5),F2
注意,如果第一个数字为零将产生不了希望的结果;在那种情况下,4B将被替换为填充字
符40,因此再次需要有效数字开始符。
MVC PLINE+1(5),=X'40214B2020'
ED PLINE+1(5),F2
因此,如果F2包含012C,后面这段编辑程序将.12(即X'40404BF1F2')置入PLINE中。

例4:假定F2为两个字节长,但总是至多包含两位数字的数。
MVC PLINE+1(3),=X'402120'
ED PLINE+1(3),F2
将会把希望的值填入PLINE吗?答案是不会,这是程序员常犯的一个错误。任何包含一个压
缩十进制数的字段将包含奇数个数字,因此,模式中数字选择符和有效数字开始符的个数之
和应为奇数。例如,如果F2包含010C。
MVC PLINE+1(3),=X'402120'
ED PLINE+1(3),F2
只将1置入PLINE(即X'4040F1'),而:
MVC PLINE+1(4),=X'40202120'
ED PLINE+1(4),F2
将10(即X'4040F1F0)置入PLINE。当然在只需要引导数字的情况下,20和21的总数为偶数是
可行的,问题的关键在于构造模式时源数字必须和模式仔细匹配。

例5:假定F2是一个包含一个要以如下形式打印的数的5字节字段:
X,XXX,XXX.XX
自然地,要删除前导零和逗号,即源000000000C应将.00置入PLINE中,模式应该为:
MVC PLINE+1(13),=X'40206B2020206B2020214B2020'
ED PLINE+1(13),F2
注意使用了一个有效数字开始符。

例6:假定F2为3字节长包含一个5位数字的数,该数的形式欲为XXX.XX,这里的数代
表一个要打印在支票上的钱,一个1.23之类的数经常希望打印为:
**1.23
在这种情况下,前导零用星号而不是空格替换,这通过将填充字符说明为星号即可:
MVC PLINE+1(7),=X'5C2020214B2020'
ED PLINE+1(7),F2

例7:假定F2包含一个三位数字的负数。打印一个符号表示的最简单的办法就是使用:
XXX
作为正数形式。及使用:
XXX-
作为负数形式。这可以通过使用:
MVC PLINE+1(5),=X'4020212060'

- 154 -
ED PLINE+1(5),F2
这里60(即-)是一个消息字符。当处理其最右部字符指示一个正数(A、C、E或F)的字节时,
有效指示器状态置为关,因此,模式中任何紧接其后的消息字符或其它字符将替换为填充字
符,如果遇到一个字符指示为负数(B或D)后,则有效指示器状态保留为开不变,这些细节
在本章的最后谈及。有时候,以下形式:
XXX CR
也用作负数的模式。此时,模式应为:
MVC PLINE+1(7),=X'4020212040C3D9'
ED PLINE+1(7),F2

例8:假定F2和F3是相邻的两字节字段,每个包含一个形式为X.XX的数。要在这两个数
间打印空格,应使用一个字段分隔符。例如:
MVC PLINE+1(10),=X'40214B202022214B2020'
将以格式X.XX X.XX的格式将两个数放入PLINE。

例9,假定F2和F3包含一个要以XX XX形式打印的两位数字的数。注意:
MVC PLINE+1(4),=X'40202120'
ED PLINE+1(4),F2
MVC PLINE+4(4),=X'40202120'
ED PLINE+4(4),F3
并不会产生所希望的结果,F3的模式将把第一个数最后一位数字覆盖掉,应代之于使用如
下代码:
MVC PLINE+4(4),=X'40202120'
ED PLINE+4(4),F3
MVC PLINE+1(4),=X'40202120'
ED PLINE+1(4),F2
通常,上述问题可以在同一行上自右自左地通过编辑要打印的字段避免。
程序8-1解释有关这一点的用法。

TITLE ‘PAYROLL EXAMPLE UTILIZING PACKED DECIMAL INSTRUCTIONS’


****************************************************************************************************
*
* 这个程序用来计算员工工资。输入记录必须依照下面的格式
* 1-20 员工姓名
* 21-22 工作时数
* 23-26 工资比率如$XX.XX,其中的“$”和“.”在输入记录中不出现
* 27-81 空白
* 如果一个员工的工作时数超过 40 小时,超出的部分将按照他工资的 1.5 倍计算。程序的*
逻辑结构如下:
* 第 1 步: 调用 PRNTHDR 打印第一页的页头
* 第 2 步: 读取第一个记录
* 第 3 步: 如果读取记录结束,则执行第 11 步
* 第 4 步: 把记录上的各个域转换成压缩十进制
* 第 5 步: 如果员工的工作时数大于 40 小时,则执行第 7 步

- 155 -
* 第 6 步: 计算 员工工资=工资比率*工作时数,执行第 8 步
* 第 7 步: 计算 员工工资=(工资比率*40)+((工资比率*1.5)*(工作时数-40))
* 第 8 步: 构造打印行
* 第 9 步: 调用 PRNTLINE 打印构造的行(如果必要的话,还要打印新的页头)
* 第 10 步: 读取下一个输入记录,转步骤 3
* 第 11 步: 退出
*********************************************************************************************
*
PAYROLL CSECT
USING PAYROLL,R15
***<第 1 步> 打印第一页的页头
*
BAL R12,PRNTHDR
*
***<第 2 步> 读取第一个记录
*
XREAD CARD,80
*
***<第 3 步> 如果读取结束,转到第 11 步执行
*
TESTEND BC B’0100’,EXIT
*
***<第 4 步> 把域转换成压缩十进制
*
PACK HOURS,HOURSIN 压缩工作时间数据
PACK RATE,RATEIN 压缩工资率数据
*
***<第 5 步> 如果超过规定工作时数,转到第 7 步执行
*
CP HOURS,=P’40’ 超过 40 小时则按照超时计算
BH OVERTIME
*
***<第 6 步> 计算 员工工资=工资比率*工作时数
*
ZAP PAY,RATE
MP PAY,HOURS
B BLDPRNT 转去构造打印行语句
*
***<第 7 步> 员工工资=(工资比率*40)+((工资比率*1.5)*(工作时数-40))
*
OVERTIME ZAP PAY,RATE
MP PAY,=P’40’ 得到 40 小时的工资数
ZAP OVTIME,RATE 保存工资比率
MP OVTIME,=P’15’ 得到 1.5 倍的工资比率

- 156 -
DP OVTIME,=P’10’
ZAP OVHRS,HOURS 计算超过规定的工作时数
SP OVHRS,=P’40’ OVHRS Å 超出的工作时数
MP OVTIME(6),OVHRS
AP PAY,OVTIME(6) PAY 中此时包含工资总数
*
***<第 8 步> 构造打印行
*
BLDPRNT MVC NAMEOUT,NAMEIN
MVC RATEOUT,=X’40202021B2020’
ED RATEOUT,RATE
*
MVC HOURSOUT,=X’40202120’
ED HOURSOUT,HOURS
*
MVC PAYOUT,=X’4020206B2020214B2020’
ED PAYOUT,PAY+1
*
*
***<第 9 步> 调用 PRNTLINE 打印
*
BAL R12,PRNTLINE
*
***<第 10 步> 读取下一个记录,并转到第 3 步执行
*
XREAD CARD,80
B TESTEND
*
***<第 11 步> 退出
*
EXIT BR R14
*
*
****************************************************************************************************
* 本程序用于打印一行。如果必要的话,PRNTHDR 将被调用来开始一个新页。
* 它的逻辑如下是:
* 第 1 步: 如果 LINECTR<=50 ,转到第 3 步
* 第 2 步: 调用 PRNTHD 打印页头并重置 LINECTR
* 第 3 步: 打印行(并置下一行输送桶控制为‘ ‘)
* 第 4 步: 退出
****************************************************************************************************
*
PRNTLINE STM R0,R15,PRNTSAVE 保存调用程序的寄存器
*

- 157 -
***<第 1 步> 检查 LINECTR,看是否需要页头
*
CP LINECTR,=P’50’
BNH PRINTNOW 无须换新页
*
***<第 2 步> 调用 PRNTHDR 来打印页头
BAL R12,PRNTHDR
*
***<第 3 步> 此时打印行并且增加 LINECTR
*
PRINTNOW XPRNT PLINE,133
AP LINECTR,=P’1’ LINECTR 增 1
MVI PLINE,C’ ’ 为下一行设置 CC
*
***<第 4 步> 退出
*
LM R0,R15,PRNTSAVE 恢复调用程序的寄存器
BR R12 返回
*
PRNTSAVE DS 16F 寄存器保存区
*
**************************************************************************************************
* 这个例程用来打印页头,重置 LINECTR 并且设置 PLINE 中的打印控制以便打印下
* 一个打印行前打印两个空行。它的逻辑如下:
* 第 1 步: 打印页头
* 第 2 步: 重置 LINECTR 为 3(下一打印行是第三行)
* 第 3 步: 设置打印控制为双空行
* 第 4 步: 退出
**************************************************************************************************
*
***<第 1 步> 打印页头
*
PRINTHDR XPRNT COLHDR,133
*
***<第 2 步> 重置 LINECTR
*
ZAP LINCTR,=P’3’
*
***<第 3 步> 设置传送桶控制为双空格
*
MVI PLINE,C’0’
*
***<第 4 步> 退出
*

- 158 -
BR R12
*
********************************************************************************************
*
LTORG
*
CARD DS CL80 输入卡的输入输出缓冲区
ORG CARD
NAMEIN DS CL20 员工姓名
HOURSIN DS CL3 工作时数
RATEIN DS CL4 工资比率(XX.XX)
ORG
*
COLHDR DC CL133’1’ 页头
ORG COLHDR+4
DC C’NAME’
ORG COLHDR+23
DC C’HRS.’
ORG COLHDR+31
DC C’RATE’
ORG COLHDR+38
DC C’TOTAL PAY’
ORG
*
PLINE DC CL133 ‘ ‘ 打印行
ORG PLINE+1
NAMEOUT DS CL20 员工姓名
ORG PLINE+22
HOURSEOUT DS CL7 工作时数
ORG PLINE+28
RATEOUT DS CL7 工资比率
ORG PLINE+35
PAYOUT DS CL10 本周的工资总数
ORG
*
LINECTR DS PL2 行计数器(每页可打印 50 行)
*
HOURS DS PL2 工作时数
OVHRS DS PL2 超时工作时数
RATE DS PL3 工作率(XX.XX)
PAY DS PL5 本周工资总数
OVTIME DS PL8 超时工作工资
*
R0 EQU 0

- 159 -
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6
R7 EQU 7
R8 EQU 8
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END PAYROLL

程序8-1

其次我们探讨EDMK指令。
EDMK指令提供了ED指令的所有优点,并且它还可以额外地将一个指针设置到一个被
编辑字符串的第一个非零数字处。
EDMK指令的格式为:
EDMK D1(L,B1),D2(B2) (编辑并定位)

该指令在第一个操作数上具有同ED指令完全相同的效果。进一步,假定x是结果满足下
述条件的最后一个字符:
z x由源数字对20或21的替换形成;
z 在替换产生前有效指示器状态为关,替换后该状态由关置为开。
在这种情况下,R1最右边的三个字节设置为x的地址。这样的x如不存在,R1保持不变。
通常地,因为至多只有一个字符满足上述条件,最后这样的字符通常也是第一个这样的字符。
为了解释EDMK指令的执行效果,假定:
F1包含402020214B2020
F2包含01230F
F3包含00097F
则指令:
EDMK F1,F2
将F1的内容设置为4040F1F24BF3F0,并将F1+2对应的地址置入R1的最右三个字节,
R1最左边的字节不变。另一方面,指令:
EDMK F1,F3
将F1的内容替换为404040404BF9F7,而R1保持不变(非20或21的替换),在这种情形下,
有效指示器由于有效数字开始符的出现而置为开。
EDMK两个最常见的用法是在表示现金数前面插入一个$符号及在负数之前插入一个
负号。作为一个例子,假定F2是一个三字节字段,该字段包含一个有效的压缩十进制正数,

- 160 -
F1包含同前例相同的模式,如F2中的压缩十进制有三个或更多的有效数字(即F1即F2的前导
零少于三个),则指令
EDMK F1,F2
将第一个非空格地址放入R1的最右三个字节。
然而,真正需要的是无论任何情况都将R1指向第一个非空字符,这可以通过如下两条
指令完成:
LA R1,F1+4
EDMK F1,F2
第一条指令将小数点地址送入R1,如果R1没有被EDMK指令的执行改变,则小数点就
是F1中的第1个非空字符。一个完整的将数和美元符号放入F1的指令序列如下:
LA R1,F1+4
EDMK F1,F2
BCTR R1,R0
MVI 0(R1),C'$'
通过使用UNPK和OI指令,数可转换为可打印形式;在大多数情况下,应使用ED或EDMK
指令,此处总结了关于编辑输出的讨论,并假定此后读者能够编辑出需要的所有输出。
程序8-2解释了到目前为止的各种技术,你应仔细连同表8-1一起研究。表8-1是一个打印
输出的例子,给定初始的资金余额是$10,000,目标资金余额$20,000及10%的年利息,每
年4次计息。

INTEREST BEGINNING
PERIOD BALANCE INTEREST END BALANCE
1 10,000.00 250.00 10,250.00
2 10,250.00 256.25 10,506.25
3 10,506.25 262.66 10,768.91
4 10,768.91 269.22 11,038.13
5 11,038.13 275.95 11,314.08
6 11,314.08 282.85 11,596.93
7 11,596.93 289.92 11,886.85
8 11,886.85 297.17 12,184.02
9 12,184.02 304.60 12,488.62
10 12,488.62 312.22 12,800.84
11 12,800.84 320.02 13,120.86
12 13,120.86 328.02 13,448.88
13 13,448.88 336.22 13,785.10
14 13,785.10 344.63 14,129.73
15 14,129.73 353.24 14,482.97
16 14,482.97 362.07 14,845.04
17 14,845.04 371.13 15,216.17
18 15,216.17 380.40 15,596.57
19 15,596.57 389.91 15,986.48
20 15,986.48 399.66 16,386.14
21 16,386.14 409.65 16,795.79
22 16,795.79 419.89 17,215.68

- 161 -
INTEREST BEGINNING
PERIOD BALANCE INTEREST END BALANCE
23 17,215.68 430.39 17,646.07
24 17,646.07 441.15 18,087.22
25 18,087.22 452.18 18,539.40
26 18,539.40 463.49 19,002.89
27 19,002.89 475.07 19,477.96
28 19,477.96 486.95 19,964.91
29 19,964.91 499.12 20,464.03

表8-1 程序8-2的输出样例

TITLE ‘A PROGRAM TO SHOW THE GROWTH OF AN INVESMENT’


****************************************************************************************************************
* 本程序计算一定投资额按照固定利率的增长情况。程序的输入包括初始余额、
* 目标余额、利率、每年计息次数。程序输出一个表,表格的每一行应打印每
* 次计息时的如下信息:
* 初始余额
* 本次计息期间的利率
* 本次计息后的余额
* 当所计出的余额大于等于目标余额是程序结束。
*
* 程序的输入记录格式如下:
* 列 内容
* 1-7 初始余额(单位为分)
* 9-15 目标余额(单位为分)
* 17-22 年利率(四个十进制位)
* 24-25 每年计息次数
*
* 程序的逻辑如下:
* 步骤 1:读入输入记录;
* 步骤 2:调用 PRNTHDRS 打印第一页的页头;
* 步骤 3:如果现在的余额已经大于等于目标余额,转步骤 8;
* 步骤 4:计算余额;
* 步骤 5:调用 PRNTLINE 打印计息信息;
* 步骤 6:把本次计息期的利息加入总利息;
* 步骤 7:把本次计息期的利息加入余额;
* 步骤 8:计算每个计息期的平均利息;
* 步骤 9:打印一行;
* 步骤 10:退出。
*************************************************************************************************
*
GROWTH CSECT
USING GROWTH,R15

- 162 -
*
***<步骤 1> 读取输入并得到初始值
*
XREAD CARD,80 读输入记录
PACK CURPRIN,BEGPRIN 取得初始余额
PACK TARGPRIN,TARGAMNT 和目标余额
*
PACK WORK,ANNULINT 计算每一个计息期的利息
SRP WORK,3,0 年利率*1000
PACK NUMPERYR,INTPERDS
DP WORK,NUMPERYR 除以每年的计息期数
SRP WORK(6),(64-3),5 除以 1000 ,四舍五入
ZAP INTPER,WORK(6) 每一个周期的利息
*
***<步骤 2> 打印每一页的页头
*
BAL R12,PRNTHDRS
*
***<步骤 3> 测试计算是否完成
*
CALCLOOP CP CURPRIN,TARGPRIN 已经到达目标余额吗?
BNL PRNTLAST 为真Æ打印统计信息
*
***<步骤 4> 计算下一个计息期的利息并且累加
*
ZAP WORK,CURPRIN
MP WORK,INTPER 余额乘以利率
SRP WORK,(64-6),5 四舍五入到分
ZAP CURINT,WORK
*
ZAP NEWPRIN,CURPRIN 利息加入本期余额
AP NEWPRIN,CURINT
*
AP PERIOD,=P’1’
MVC OINTPER,=X’40202120’
ED OINTPER,PERIOD 加入计息期序号
*
MVC OBEGBAL,=X’4020206B2020214B2020’
ED BOEGBAL,CURPRIN 加入初始余额
*
MVC OINTERST,=X’4020206B2020214B2020’
ED OINTERST,CURINT 加入每一个计息期的利息
*
MVC OENDBAL,=X’ 4020206B2020214B2020’

- 163 -
ED OENDBAL,NEWPRIN 加入新的余额
*
***<步骤 5> 调用 PRNTLINE 打印一行
*
BAL R12,PRNTLINE
*
***<步骤 6> 累计总的利息
*
AP TOTINT,CURINT
*
***<步骤 7> 重置当前的余额为新值
*
ZAP CURPRIN,NEWPRIN
B CALCLOOP
*
***<步骤 8> 计算每个计息期平均利息
*
PRNTLAST SRP TOTINT,1,0 利息乘以总数 INT*10
DP TOTINT,PERIOD
SRP TOTINT(6),(64-1 ),5 得到平均利息
*
LA R1 , AVGINT+7
MVC AVGINT,=X’ 4020206B2020214B2020’
EDMK AVGINT,TOTINT+2
BCTR R1,0
MVI 0(R1),C’$’ R1 指向第一位
*
***<步骤 9> 打印最后一行
*
XPRNT LASTLINE , 35
*
***<步骤 10> 退出
*
BR R14
*
****************************************************************************************************
* 调用这个例程是用于打印一行。如果必要,PRNTHDRS 将被调用从而开始一个新页。
* 程序的逻辑如下:
* 步骤 1:如果 LINECTR <=50 ,转第 3 步执行;
* 步骤 2:调用 PRNTHDRS 打印页头和重置 LINECTR.;
* 步骤 3:打印一行(并且设置下一行的打印控制为’ ‘);
* 步骤 4:退出。
***************************************************************************************************
*

- 164 -
PRNTLINE STM R0,R15,PRNTSAVE 保存调用程序的寄存器
*
***<步骤 1> 检查 LINECTR,看是否需要页头
*
CP LINECTR,=P’50’
BNH PRINTNOW 不需要
*
***<步骤 2> 调用 PRNTHDRS 打印页头
*
BAL R12,PRNTHDRS
*
***<步骤 3> 此时打印一行并且增加 LINECTR
*
PRINTNOW XPRNT PLINE,133
AP LINECTR,=’1’ LINECTR 增 1
MVI PLINE,C’ ‘ 为下一行设置 CC
*
***<步骤 4> 退出
*
LM R0,R15,PRNTSAVE 恢复调用程序寄存器
BR R12 返回
*
PRNTSAVE DS 16F 寄存器保存空间
*
***************************************************************************************************
* 这个例程用来打印页头,重置 LINECTR 并且设置 PLINE 中的打印控制以使下一
* 打印行打印之前先打印双空行。程序的逻辑是:
* 步骤 1: 打印两行页头;
* 步骤 2: 重置 LINECTR 为 4(下一打印行是第 4 行);
* 步骤 3: 设置打印控制为双空行;
* 步骤 4: 退出。
***************************************************************************************************
*
***<第 1 步> 打印页头
*
PRNTHDRS XPRNT COLHDR1,133
XPRNT COLHDR2,133
*
***<第 2 步> 重置 LINECTR
*
ZAP LINECTR ,=P’4’
*
***<第 3 步> 设置打印控制为双空行
*

- 165 -
MVI PLINE,C’0’
*
***<第 4 步> 退出
*
BR R12
*************************************************************************************************
LTORG
*
CARD DS CL80 输入数据空间
ORG CARD
BEGPRIN DS CL7 初始余额
ORG CARD+8
TARGAMNT DS CL7 目标余额
ORG CARD+16
ANNULINT DS CL6 年度利率(4 个十进制位)
ORG CARD+23
INTPERDS DS CL2 每年的计息期数
ORG
*
COLHDR1 DC CL133’1’ 第一页页头
ORG COLHDR1+2
DC C’INTEREST’
ORG COLHDR1+25
DC C’BEGINNING’
ORG COLHDR1+40
DC C’INTEREST’
ORG COLHDR1+55
DC C’END BALANCE’
ORG
COLHDR2 DC CL133’ ’
ORG COLHDR1+2
DC C’PERIOD’
ORG COLHDR2+26
DC C’BALANCE’
ORG
*
PLINE DC CL133’ ’ 打印行
ORG PLINE+1
OINTPER DS CL4 利息周期序号
ORG PLINE+23
OBEGBAL DS CL10 初始余额
ORG PLINE+38
OINTERST DS CL10 本周期的利息
ORG PLINE+53

- 166 -
OENDBAL DS CL10 新的余额
ORG
*
LASTLINE DC C’0’
DC CL10’ ’
DC C’AVG INTEREST =’
AVGINT DS CL10
*
*
CURINT DS PL4 每个计息期的利息
CURPRIN DS PL4 当前的余额
NEWPRIN DS PL4 新的余额
TARGPRIN DS PL4 目标余额
*
INTPER DS PL4 每个计息期的平均利息(4 个十进
* 制位)
PERIOD DS PL2’0’ 计息周期序号(FROM 1)
NUMPERYR DS PL2 每年的计息周期数
*
TOTINT DC PL8’0’ 总的利息数
*
WORK DS PL8 工作区
LINECTR DS PL2 行计数器
*
R0 EQU 0
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6
R7 EQU 7
R8 EQU 8
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END GROWTH

程序8-2

- 167 -
第五节 十进制移位操作

SRP指令可对一个压缩十进制数进行移位操作。我们都知道十进制数左移一位等价于乘
以10,右移一位等价于除以10。更进一步,SRP指令可用于对右移结果进行近似处理,为了
理解这一指令完成的操作,必须了解移位方向及移位数字数如何以6位二进制形式编码,这
一编码规则如下所示:
z 31个位置以内的左移由移位位数的二进制数表示。例如,左移9位数字表示为001001。
z 1至32位右移由64减去移位位数表示,并将结果表示为6位二进制数,例如,右移1位可
以表示为:
64
- 1
63 =111111
记住这一点,将会清楚如下SRP指令的细节描述。
SRP指令的格式如下:
SRP D1(L,B1),D2(B2),i (移位及近似十进制)

指令的执行将把第1个操作数指定的字段内容移位。移位的方向和位数由以下内容确定:
z 计算D2(B2)表示的地址
z 使用这一地址的最右边6位作为上述描述的编码形式来解析出移位的方向和位数。
除非结果为0,否则符号不变;在结果为0情形下,符号总是置成正数。如果右移,值i
(范围0~9)用作近似因子,i加到移出右边的数字最左边的数字上,如果产生的结果大于9,
1加到移位后的结果上。如果左移非零数字丢失,就产生了通常引起程序中止的溢出。条件
码设置如下:
CC 含义
0 结果为0
1 结果为负
2 结果为正
3 溢出
本指令的操作码为FD。
例如,指令:
SRP RATE,B'000100',0
将产生四位左移。指令:
SRP TIME,B'111111',5
产生一位右移,并使用5作为近似因子。指令:
SRP WAGE,0(R2),0
可产生右移或左移,由R2最右边6位的内容确定。
当第2个操作数不涉及寄存器时,可以说明一个十进制数值。在这种情况下,十进制数
值被汇编程序转换为等价的二进制,这会带来如下方便:
z 左移n位可以表示为n
z 右移n位可以表示为64-n
因此,先前引用的两个SRP例子可以编写如下:

- 168 -
SRP RATE,4,0

SRP TIME,(64-1),5

第六节 十进制与二进制之间的转换

到目前为止给出的例子中,ASSIST宏指令XDECI和XDECO用于字符格式的数和二进制
数之间的转换,遗憾的是,这些宏指令在大部分汇编程序中不能使用,本节将介绍转换压缩
十进制数到二进制或二进制到压缩十进制数的指令。然后,指出二进制和字符表示之间的标
准转换方法。

1.转换二进制指令CVB:
转换二进制指令CVB的格式为:
CVB r, D(X,B) (转换二进制)

指令的执行将把D(X,B)地址处的压缩十进制转换成二进制表示。如果D(X,B)不是一个
双字边界地址,就会产生一个数据规格异常错误。如果D(X,B)是一个双字边界地址但不包含
一个合法的压缩二进制数,就会产生一个数据异常错误。最终,如果D(X,B)处的压缩十进制
数太大而不能用32位表示,就会产生一个定点除法异常错误。本指令的操作码为4F。
例如,假定DWORD为一个双字,其内容为000000000000010F,则指令:
CVB R1,DWORD
将把R1的内容替换为0000000A。

2.转换十进制指令CVD:
转换十进制指令CVD格式如下:
CVD r, D(X,B) (转换十进制)

指令的执行将把D(X,B)处双字的内容替换为r中二进制数对应的十进制压缩形式。如果
D(X,B)不是双字边界地址,则会产生一个数据规格异常错误。本指令的操作码为4E。
例如,如果R1包含FFFFFFFF,则指令:
CVD R1,DWORD
将D(X,B)的内容替换为000000000000001D。实际上,必须做出使用哪一个负号的决定。然
而,对读者来说,现在假定使用某些合法负数符号就足够了。

3.转换的标准技术
数的字符表示转换到二进制表示的标准技术是:首先通过使用PACK指令将数转换为压
缩十进制数格式,然后通过使用CVB指令将产生的压缩十进制数转换为二进制。相反的过
程用CVD指令再用UNPK指令将二进制数转换为字符表示。这一过程图示8-3所示:
再次提醒读者,UNPK指令实际上将一个数转换为非压缩十进制格式,如果希望字符格
式的话,应该使用OI指令确保符号为F。

- 169 -
PACK CVB
字符格式数据 压缩十进制数 二进制格式数据

完成二进制运算

UNPK CVD
字符格式的结果 压缩十进制表 二进制表示
示的结果 的结果

图8-3 标准十/二进制数据转换流程图

习题八

1、对每个下面的练习,写出指令执行后FLD1的值:
FLD1 FLD2 指令 FLD1的值
a. 01234F F0F1F2 PACK FLD1, FLD2
b. AB PACK FLD1, FLD1
c. F1F2F3F PACK FLD1, FLD1
d.000000 ABCDEF PACK FLD1,FLD2
e.000000 123F UNPK FLD1,FLD2
f.1234567D 026C UNPK FLD1,FLD2
g.ABCDEF UNPK FLD1,FLD1
h.40404040 4D UNPK FLD1,FLD2

2、对每个下面的练习,写出当执行了指令后的FLD1中的值
FLD1 FLD2 指令 FLD1的值
a. 01234F 001D AP FLD1, FLD2
b. 0124F 00001D SP FLD1, FLD2
c. FFFF 021F ZAP FLD1, FLD2
d.00200D 100F AP FLD1,FLD2
e.00100D 020D SP FLD1,FLD2
f.40404040 000C ZAP FLD1,FLD2

3、对每个下面的练习,写出当执行了指令后FLD1中的值

- 170 -
FLD1 FLD2 指令 FLD1的值
a. 00004F 020F MP FLD1, FLD2
b. 00013D 9C MP FLD1, FLD2
c. 0000200D 150D MP FLD1, FLD2
d. 01000F 500C DP FLD1,FLD2
e. 02010D 5D DP FLD1,FLD2
f. 0000100C 020C DP FLD1,FLD2

4、检查下面模式和源字段,接着填空,第一行已作为演示完成。
模式 数值
P1:40 20 20 20 F1:12 3C
P2:40 20 21 20 F2:00 0C
P3:5C 20 21 20 F3:01 2C
P4:40 20 4B 20 20 F4:12 34 56 7C
P5:40 21 4B 20 20 F5:00 00 12 3C
P6:40 20 20 6B 20 20 21 4B 20 20 F6:00 00 00 0C
P7:40 20 20 6B 20 20 21 4B 20 20 40 C3 D9 F7:00 01 23 4D
指令 模式结果 字符串
ED P1(4),F1 40 F1 F2 F3 123
a.ED P1(4),F2 __ __ __ __ __________
b.ED P1(4),F3 __ __ __ __ __________
c.ED P2(4),F1 __ __ __ __ __________
d.ED P2(4),F2 __ __ __ __ __________
e.ED P2(4),F3 __ __ __ __ __________
f.ED P3(4),F1 __ __ __ __ __________
g.ED P3(4),F2 __ __ __ __ __________
h.ED P3(4),F3 __ __ __ __ __________
i. ED P4(5),F1 __ __ __ __ __ __________
j. ED P4(5),F2 __ __ __ __ __ __________
k.ED P4(5),F3 __ __ __ __ __ __________
l. ED P5(5),F1 __ __ __ __ __ __________
m.ED P5(5),F2 __ __ __ __ __ __________
n.ED P5(5),F3 __ __ __ __ __ __________
o.ED P6(10),F4__ __ __ __ __ __ __ __ __ __ __________
p.ED P6(10),F5__ __ __ __ __ __ __ __ __ __ __________
q.ED P6(10),F6__ __ __ __ __ __ __ __ __ __ __________
r.ED P7(13),F4__ __ __ __ __ __ __ __ __ __ __ __ __ __________
s.ED P7(13),F5__ __ __ __ __ __ __ __ __ __ __ __ __ __________
t.ED P7(13),F6__ __ __ __ __ __ __ __ __ __ __ __ __ __________
u.ED P7(13),F7__ __ __ __ __ __ __ __ __ __ __ __ __ __________

5、对下面的每个练习,显示当指令执行后FLD1中的值
FLD1 指令 FLD1的值
1. 00004F SRP FLD1,4,0 __________

- 171 -
2. 00013D SRP FLD1,2,0 __________
3.0000262C SRP FLD1,(64-2),5 __________
4. 01925F SRP FLD1,(64-3),0 __________
5. 02654C SRP FLD1,(64-2),5 __________
6. 0000168C SRP FLD1,2,5 __________
7. 0000168D SRP FLD1,(64-4),5 __________

6、编写一个输出天气概况报告的程序,程序要读入遵照下列格入的记录:
位置 内容
1-6 日期(MMDDYY)
7-10 华氏最高温度 (1位小数)
11-14 华氏最低温度 (1位小数)
15-18 华氏正常温度(1位小数)
程序应产生下面数值正确取整后的报告:
TEMPERATURE SUMMARY
DATE LOW(F) HIGH(F) LOW(C) HIGH(C) NORMAL(F) DIFF
6/16/89 62.3 76.7 16.8 24.8 62.6 .3-
6/17/89 64.6 79.3 18.1 26.3 62.8 1.8
: : : : :
AVERAGES:63.5 78.1 17.5 25.6

7、世界有许多不同形式的贷币,如果你去旅游,你会发觉自己在兑换货币(比如,你会发现
100美元可以兑换13.984日元),相应的币值随货币而变动,有时导致出现这种情况,通
过兑换几种货币元后,你实际最后获得的钱比你开始时多一点。例如,你可以用100美
元13.894日元,而这又可换成144.775意大利里拉,最后又可以换成100.47美元,这种
交易被称为套汇,显然这种情形只能持续很短的一段时间,交易人会立即开始利用小的
价格优势将价格变得“一致”(就某种意义上讲,贬值的货币价格将抬高一点而价高的货
币将会稍微贬值),使用计算机,有可能很迅速的发现这样的机会,在这个作业中,你将
分别读入六种货币的“兑换因子”,这些货币分别为意大利、德国、日本、英国、哥伦
比亚和美国的货币,输入到程序中的将是遵照下面格式的21个记录:
位置 内容
1-3 国家代码 1(ITA, WGR, JAP, BRT, or USA)
4-6 国家代码 2
7-16 货贝1换为货贝2兑换因子
(XXXX.XXXXXX)
注意第一个国家转换到第二个国家的兑换因子可用公式(1/第2国转换第1国的兑换因子)
计算,作为测试输入,你可使用:
ITAWGR0731123911
ITAJAP0010370317
ITABRT2324207400
ITACOL0003946685
ITAUSA1440922103
WGRJAP0000014183
WGRBRT0003178952

- 172 -
WGRCOL0000005396
WGRUSA0001970832
JAPBRT0224121170
JAPUSA0138946780
BRTCOL0000001699
BRTUSA0000619963
COLUSA0365096745
先要打印一个类似下面的表:
CURRENCY EXCHANGE VALUES
ITA WGR JAP BRT COL USA
ITA 0.01368 .096429 .000430 .253377 .000694
WGR 731.123911 70.506944 .314569 185.322460 .507400
: : : : : : :
USA 1440.922103 1.970832 138.946780 .619963 365.096745

这样,先应该计算完整的兑换因子且打印整个表(应该产生一个6×6的这些国家构成的
表格)。接着,你的程序应寻找“机会”,即它要寻找三种货币(它们为A、B和C)这样你
能进行A→B→C→A兑换,最终得到比开始时显著多的钱,为了确认“显著多”的含义,
程序应当读入一个最后记录它在位置1--5(X.XXXX)包含一个百分值。显著多即意味着最
后结果比原来的值按给定的最少百分值多。例如,你的输入百分比为
00500 (即., 0.05%)
那么交易
BRT→JAP→COL→BRT 同其增加的百分值一起打印
为了寻找这样的机会,程序需用类似下面的算法:
for (each of the 6 countries)
Call the chosen country A
for (each of the remaining 5 countries)
Call the chosen country B
for (each of the remaining 4 countries)
Call the chosen country C
If (A→B→C→A would produce a significant increase)
report on the A→B→C→A opportunity
注意有6×5×4=120种可能必须考虑的交易。

8、编写一个程序计算一平方英尺小块土地的平均开销,特别地,程序应完成下面的任务。
a.程序要读入一组格式如下的记录:
位置 内容
1-4 宽度(英寸)
5-8 长度(英寸)
9-15 价格
读记录时,应产生一个与下面相似的报告:
WIDTH LENGTH AREA COST COST/SQ.FT.
100 50 5000 9,000 1.80
150 200 30000 6,000 .20

- 173 -
: : : : :
b. 在文件的末尾,单独产生下面格式的一行:
TOTAL AREA=84,000 TOTAL COST=$202,000 AVG.COST/SQ.FT.=$2.40
所有的算术操作必须用十进制算术指令。

9、编写一个程序产生贷款的分期偿付表,输入将指示贷款的本金、当时的利率和每月的付
帐,程序产生的输出为一张表,它指示贷款期间的每个月的付款编号、当月应付款、本
金月息、当月实付款、本金余额,所有算术操作应用十进制指令完成。输入格式如下:
位置 内容
1-7 本金(美分)
10-15 年利(四位小数)
20-25 当月付款
例如,一笔以年利率6%的1000.00美元贷款(月息0.5%)按每月付100美元,将表示如下:
Col. 1234567890123456789012345678901234567890123456789012345
0100000 060000 010000
所产生的输出表应包含下列五列:
Col1:付款编号,以零开始
Col2:当月应付款(最后一次所需付款比标准的全部付款要少)
Col3:本金月息,上月本金余额乘以年利率的十二分之一按分取整
Col4:当月实付款
Col5:本金余额
例如:由测试数据产生的贷款的描述如下:
PAYMENT PRINCIPAL OUTSTANDING
NUMBER PAYMENT INTEREST REPAYMENT PRINCIPAL
0 1,000.00
1 100.00 5.00 95.00 905.00
2 100.00 4.53 95.47 809.53
3 100.00 4.05 95.95 713.58
4 100.00 3.57 96.43 617.15
5 100.00 3.09 96.91 520.24
6 100.00 2.60 97.40 422.84
7 100.00 2.11 97.89 324.95
8 100.00 1.62 98.38 226.57
9 100.00 1.13 98.87 127.70
10 100.00 . 64 99.66 28.34
11 28.48 .14 28.34 0.00

- 174 -
第九章 高级指令介绍

第一节 逻辑运算指令

在本节中,我们将介绍能用于完成两个全字之间、立即数字节和存贮器字节以及存贮器
中两个字段之间的逻辑操作指令。
常用的逻辑运算包括逻辑与(AND)、逻辑或(OR)、逻辑异或(XOR)和逻辑非(NOT)。
下表9-1中列出了这四种逻辑运算的规则。对这些规则的熟悉有助于讨论和理解本节中将要
介绍的指令。

逻辑与(AND) 逻辑或(OR) 逻辑异或(XOR) 逻辑非(NOT)


位 位 位 位
1 2 结果 1 2 结果 1 2 结果 1 结果
0 0 0 0 0 0 0 0 0 0 1
0 1 0 0 1 1 0 1 1 1 0
1 0 0 1 0 1 1 0 1
1 1 1 1 1 1 1 1 0

表9-1 逻辑运算规则

如下规则很有用:
z 当对两个二进制位进行AND操作时,仅当两位均为1时结果为1,否则结果为0。
z 当对两个二进制位进行OR操作时,仅当两位均为0时结果为0,否则结果为1。
z 当对两个二进制位进行XOR操作时,仅当两位的值不同时结果为1;如果两位具
有相同的值则结果为0。
这些操作可以扩展到任意长度的位串。两个长度相同的位串完成一个逻辑操作的结果由
每个相应的位对完成那个逻辑操作的结果确定。这个过程由如下例子解释。

例1、AND操作
第一个串 0000 0101 110101
第二个串 1111 1001 011100
结果 0000 0001 010100

例2、OR操作
第一个串 0000 0101 110101
第二个串 1111 1001 011100
结果 1111 1101 111101

例3、XOR操作
第一个串 0000 0101 110101
第二个串 1111 1001 011100
结果 1111 1100 101001

- 175 -
在全字整数间实现这些逻辑操作的指令为N、NR、O、OR、X以及XR。下面讨论这些
指令。

1.逻辑与指令N
逻辑与指令N的格式为:
N r, D(X,B) (逻辑与)
功能:
(r)←(r)AND(D+(X)+(B))

指令的执行将在寄存器r及地址D(X,B)处的两个全字上完成一个AND操作。结果替换寄
存器中的操作数;第二个操作数不变。如果结果的每位值为0,条件码设为0;否则条件码设
为1。本指令的操作码为54。

2.寄存器逻辑与指令NR
寄存器逻辑与指令NR的格式为:
NR r1, r2 (寄存器逻辑与)
功能:(r1)←(r1)AND(r2)

本指令除了第二个操作数是寄存器r2外,指令的执行类似于N指令。如果结果的每位值
为0,条件码设为0;否则条件码设为1。本指令的操作码为14。

逻辑与指令的用法的关键在于如下:参与运算的两个二进制数位,若第一个数位为0,
则结果为0(即不必考察第二个数位),若第一个数位为1,则结果的值与第二个数位的值相
同。
关于这一点的一个更清楚但不太精确的描述是:任意位和0进行逻辑与操作产生0;任何
位和1进行操作保持该位不变。
因此,指令:
N R1,=X'00000000'
将R1清零,而指令:
N R1,=X'FFFFFFFF'
将不改变R1的内容。如下例子解释了逻辑与指令的使用:

例1、假定R1包含一个地址,该地址或许不在全字边界,为了确使这个地址调整到一个全字
边界,可以向下取整到下一个全字边界,做法上只要将R1最右边的两位的值改变为0就足够
了(即使得R1的地址为4的倍数表示的数值)。如下指令:
N R1,=X'FFFFFFFC'
将完成这一目标,注意只有R1的最右边两位的值被影响。如果希望将地址向上近似而非向
下,可使用如下指令:
LA R1,3(R1)
N R1,=X'FFFFFFFC'
将二进制数往上或往下近似到最近的任何2的幂的倍数应使用类似的指令。

例2、有时有必要计算一个给定数被2幂除的余数。例如,假定R1的数被16除产生的余数必
须存于R0。实际上该余数就是R1最右边4位所表示的数。因此,指令:
L R0,=X'0000000F'

- 176 -
NR R0,R1
的执行将产生期望的结果。

3.逻辑或指令O:
逻辑或指令O的格式为:
O r, D(X,B) (逻辑或)
功能:
(r)←(r)OR(D+(X)+(B))

指令的执行将在寄存器r和地址D(X,B)处的全字上完成OR操作,结果替换寄存器r的内
容,第2个操作数不变。如果结果的每位为0,则条件码设置为0;否则设置为1。本指令的操
作码为56。

4.寄存器逻辑或指令OR:
寄存器逻辑或指令OR的格式为:
OR r1, r2 (寄存器逻辑或)
功能:
(r1)←(r1)OR(r2)

除了第2个操作数在寄存器r2中外,指令的执行具有同O指令执行类似的作用。如果结
果的每位为0,则条件码设置为0;否则设置为1。本指令的操作码为16。

使用逻辑或指令的关键在于:任意位和0进行或该位将不变,任意位和1进行或该位将被
置为1。如下的例子解释了OR指令的用法。

例1、出于节省寄存器以作他用的目的,有时希望使用一个或许已另作基址寄存器或变址寄
存器的寄存器作为标志。如果为此使用这样一个寄存器,最右边的24位不能改变(这些位是
用作地址计算的位),以便不影响该寄存器作为基址或变址寄存器。假定R12用作此目的,指
令:
O R12,=X'80000000'
可用于将最左边的标志位置为1而其它位维持原值不变。指令:
N R12,=X'7FFFFFFF'
可用作将最左边的位置成0而其它位维持原值不变。指令
LTR R12,R12
可用于测试标志条件(正负符号在最高位)。

例2、假定R2包含一个范围从0到9的数。为了包含该整数的字符表示,R2的最右边可以用如
下指令改变:
O R2,=X'000000F0'

5.逻辑异或指令X
逻辑异或指令X的格式为:
X r, D(X,B) (异或)
功能:
(r)←(r)XOR(D+(X)+(B))

- 177 -
指令的执行在寄存器r和地址D(X,B)的全字上完成异或操作。结果替换寄存器r的内容,
D(X,B)处的字保持不变。和其它逻辑指令一样,如果结果每位为0条件码设置为0;否则条件
码设置为1。指令的操作码为57。

6.寄存器逻辑异或指令XR:
寄存器逻辑异或指令XR的格式为:
XR r1,r2 (寄存器异或)
功能: (r1)←(r1)XOR(r2)

本指令不同于前述异或指令之处在于第2个操作数为寄存器r2。其余均相同。指令的操
作码为17。

异或指令的执行效果可以概括如下:任意位和0进行异或操作该位保持不变,任意位和1
进行异或操作该位求反。
异或指令的普遍用法是交换两个寄存器的内容。具体地,执行如下指令:
XR R2,R3
XR R3,R2
XR R2,R3
将R2和R3的内容互换,这三条指令的执行比如下三条指令执行的时间长。
LR R4,R2
LR R2,R3
LR R3,R4
然而XR指令序列不需要额外的寄存器。请读者自己验证三条XR指令的确交换了两个寄存器
内容。

以上六个指令在两个全字上进行操作,除此之外,S/390中还有三个SI格式指令NI、OI
及XI用于在字节上完成逻辑操作,任何这样指令的执行将在内存中的某个字节上完成逻辑
操作,而立即数字节将在指令中编码。结果替换存贮器中的字节,指令描述及其例子如下。

7.立即数逻辑与指令NI:
立即数逻辑与指令NI的格式为:
NI D(B),byte (立即数逻辑与)
功能:(D+(X)+(B) )←(D+(X)+(B))AND byte

指令的执行在第一个操作数指定的存贮器字节及第2个操作数指定的字节上完成逻辑与
操作,结果替换第1个操作数的内容,第2个操作数不变,可能出现保护错误或访问异常错误。
本指令的操作码为94。条件码CC设置如下:
CC 含义
0 产生的结果字节为00
1 结果字节不为00
2 -
3 -
8.立即数逻辑或指令OI:
立即数逻辑与指令OI的格式为:

- 178 -
OI D(B),byte (立即数逻辑或)
功能:
(D+(X)+(B) )←(D+(X)+(B))OR byte

指令的执行在第一个操作数指定的存贮器字节及第2个操作数指定的字节上完成逻辑或
操作,结果替换第1个操作数的内容,第2个操作数不变,可能出现保护错误或访问异常错误。
本指令的操作码为96。对CC的影响与NI指令相同。

9.立即数逻辑异或指令XI:
立即数逻辑与指令OI的格式为:
XI D(B),byte (立即数逻辑异或)
功能:(D+(X)+(B) )←(D+(X)+(B))XOR byte

指令的执行在第一个操作数指定的存贮器字节及第2个操作数指定的字节上完成逻辑异
或操作,结果替换第1个操作数的内容,第2个操作数不变,可能出现保护错误或访问异常错
误。本指令的操作码为97。对CC的影响与NI指令相同。

使用这三条指令的关键之处在于:
z NI指令用于将位设置为0
z OI指令用于将位设置为1
z XI指令用于将位求反
作为一个例子,假定单字节字段FIELD包含值X'27',则如下指令的执行:
OI FIELD,B'01001000'
将FIELD字节的内容改变为X'6F'。如下指令:
NI FIELD,X'0F'
将FIELD的内容改变为X'07'。如下指令:
XI FIELD,X'F0'
将FIELD的内容改变为X'D7'。
NI和OI指令特别常见的用法是用于改变分支指令中的屏蔽码。为了说明这是怎样完成
的,请注意以下指令:
BC B'0000', addr

NOP addr
均被编码为:470hIhBhDhDhD,其中470hIhBhDhDhD是地址编码。NOP指令是一个表示“无操
作”的扩展助记符名,NOP指令对应的RR类指令为为:
NOPR r
该指令的编码为070hr。NOP指令的基本用法是产生一个以后可以激活的分支指令。如下例
子展示了这是怎样做到的:
LOOP NOP NOTFIRST
OI LOOP+1,X'F0' 设置分支
……
仅执行一次的过程
……
NOTFIRST ……

- 179 -
在这种情况下,位于OI指令和NOTFIRST之间的指令序列仅执行一次。OI指令的执行激活分
支以便在通过LOOP时的时候,选NOTFIRST分支。此后在程序中,分支可以通过如下指令
的执行来关闭
NI LOOP+1,X'0F' 关闭分支
尽管改变分支屏蔽的技术很常用,越来越多的人们现在认识到这是一种应该避免的做
法。基本反对观点是程序包含这样的语句难于调试和修改,取而代之的应该是使用标志,因
为后者更清楚。
在我们结束逻辑立即指令的讨论之前,要提及一个普遍的用法。当一个压缩的十进制字
段解压时,最右边的字节由于符号的缘故可能包含一个不希望的值。在这种情况下,有必要
使用OI指令使最右边的字符变为可打印十进制数字符。因此:
UNPK SOCSEC(9),SSN(5)
OI SOCSEC+8,X'F0'
将把一个社会保险号解压成一个9字符打印格式。

指令XC、OC和NC完成内存中两个字段之间的逻辑运算,比起它们对应的RX、RR和SI
指令而言,它们的使用率要少得多,然而,在某些特殊情况下它们特别有用。

10.内存逻辑异或指令XC
内存逻辑异或指令XC的格式如下:
XC D1(L,B1),D2(B2) (内存逻辑异或)
功能:(D1+(B1))←(D1+(B1) )XOR(D2+(B2)

指令的执行在两个长度为L的地址为D1(B1)和D2(B2)处的字段上完成异或运算。结果替
换第一个操作数,指令的操作码为D7。条件码CC设置如下:
CC 含义
0 结果字节全为0
1 结果字节不全为0
2 -
3 -
XC指令有两个常见的用法:
z 字段中的每个字节清0,可以通过对字段和其自身进行异或运算来实现。例如指令:
XC TABLE,TABLE
的执行将TABLE的每个字节设置为0。
z 两个字段的内容可以通过使用三个异或操作互换。例如,指令序列
XC F1,F2
XC F2,F1
XC F1,F2
的执行将使F1和F2的内容互换。

11.内存逻辑或指令OC
内存逻辑或指令OR的格式为:
OC D1(L,B1),D2(B2) (内存逻辑或)
功能:(D1+(B1))←(D1+(B1) )OR(D2+(B2)

- 180 -
指令的执行将使两个操作数的OR操作得以完成。结果替换第一个操作数,条件码的设
置同XC指令相同。指令的操作码为D6。
在一个包含数字字符及空格的字段中,一条OC指令可用于将前导空格改变为0。例如,
指令:
OC ACCT#,=8X'F0'
将会把任何ACCT#字段的空格替换为0。

12.内存逻辑与指令NC:
内存逻辑与指令NC的格式为
NC D1(L,B1),D2(B2) (内存逻辑与)
功能:(D1+(B1))←(D1+(B1) )AND(D2+(B2)

指令的执行将在两个操作数上完成AND操作。结果替换第1个操作数,条件码的设置同
XC和OC指令相同。指令的操作码为D4。NC指令比OC和XC指令用得少些,使用NC指令的
例子将在本章最后一节中给出。

第二节 屏蔽测试运算

屏蔽测试运算TM指令是一个SI格式指令,可用于测试指定存贮器中字节的选定位。立
即字节中的位值决定要测试存贮器字节中的哪些位,存贮器字节中相应于立即字节中值为1
的哪些位被测试,而那些相应于立即字节值为0的位不被测试,正因这个原因,TM指令中编
码的立即字节被称为屏蔽(mask)。
屏蔽测试运算TM指令的格式如下:
TM D(B),mask (屏蔽测试)

指令的操作码为91。指令的执行仅影响条件码,条件码根据如下规则设置:
z 如果地址D(B)处字节的相应于mask中值1的位的每一位具有值0,则条件码设置为
0(选定的位全为0);
z 如果地址D(B)处字节的相应于mask中值为1的位的每一位具有值1,则条件码设置为
3(选定的位全为1);
z 在其它任何情况下,条件码设置为1。
例如,假定FLD包含X'0E',则指令:
TM FLD,B'10010000'
将设置条件码CC为0,因为FLD的位0和位3均为0。类似地,指令:
TM FLD,B'10001000'
将设置条件码为1。以及指令:
TM FLD,B'00001110'
将设置条件码为3。
可于TM之后使用指令的扩展助记符集列于附录中,请读者查阅该表后阅读如下指令序
列:
TM WORD+3,B'00000011'
BNI NOTMUT4

- 181 -
本指令序列如果选定的其中一位或两位均不为0将产生一个分支,这一转移仅当WORD的值
不是4的倍数将会产生。
TM指令的最普遍的用法是测试标志位。当几位中的每一位都用作标志时,这将是很方
便的。例如,假定FLAGBYTE已被定义为:
FLAGBYTE DC X'00'
* 位 0-4 未用
* 位 5=1 产生了文件结束
* 位 6=1 无效的帐号ACCOUNT#
* 位 7=1 无效的数额字段
进一步假定调用某些内部子程序读取一个记录,设置FLAGBYTE以反映读入操作的结果。
在这种情况下,如下指令序列依赖于标志位的设置,将引起控制转移到合适的子程序:
TM FLAGBYTE,B'00000100' =1 EOF
BO EOF
TM FLAGBYTE,B'00000010' =1 无效帐号
BO BADACCT#
TM FLAGBYTE,B'00000001' =1 无效帐号
BO BADAMNT
注意:OI和NI指令可用于将标志位设置为1和0。例如,指令:
OI FLAGBYTE,B'00000100'
可用于设置EOF指示器,指令:
NI FLAGBYTE,B'11111110'
可用于将BADAMNT标志设置为0。

第三节 移位操作

移位操作是一种用途广泛的操作。移位操作分为逻辑移位和算术移位两大类。本节中我
们将进行讨论。
在一个含有m位的二进制数上,完成逻辑右移n位的操作可以通过如下过程得到:
1、从原来的数中移去最右边的n位数字
2、将剩下的数字都往右移n位
3、将n个零放于所得结果最左边数字的左边。
例如,完成一个三位逻辑右移的过程图解如下:

被删除掉
1 0 1 1 0 0 0 1

0 0 0 1 0 1 1 0
被加入

逻辑左移以类似的方式完成。左移10110001三位的过程图示如下:

- 182 -
被删除掉
1 0 1 1 0 0 0 1

1 0 0 0 1 0 0 0
被加入

指令SLL和SRL在寄存器的内容上完成逻辑移位,而指令SLDL和SRDL在偶/奇存器对的
内容上完成逻辑移位,这四个指令均为RS格式指令,但其格式不同于其它RS格式指令。RS
指令标准格式为:
标号 助记符 r1, r2,D(B)
而移位指令的格式为:
标号 助记符 r1, D(B)
在两种情况下,产生的机器指令的格式均为:

hOhOhr1hr2 hBhDhDhD
然而,当对移位指令编码时,hr2自动地设置为0。

1.逻辑左移单寄存器指令SLL
逻辑左移单寄存器指令SLL的格式为:
SLL r1, D(B) (逻辑左移单寄存器)

指令的执行将使寄存器r1的内容逻辑左移。被删掉的数字丢失,指令的操作码为89。条
件码CC不受影响。移位为n位逻辑移位,n的计算方式如下:
⑴.计算D(B)地址。
⑵.n被设置到上述结果的最右边6位。
这一计算对所有移位指令均有效,这意味着可以说明移位达63个位置的移位,但请不要对单
寄存器使用超过31位的移位。

2.逻辑右移单寄存器指令SRL:
SRL r1, D(B) (逻辑右移单寄存器)

指令的执行对r1寄存器内容进行逻辑右移,在所有其它方面,它类似于SLL指令。指令
的操作码为88。
为了解释SLL和SRL指令的用法,假定:
R1 包含0F0F0F0F
R2 包含00000005
指令:
SLL R1,2
将R1的内容改变为3C3C3C3C。指令:
SLL R1,0(R2)
将R1的内容改变为E1E1E1E0。注意,在上述第二种情况下,要移的位数由R2的内容确定(即
要移5位)。指令:

- 183 -
SRL R1,6
将R1的内容改变为003C3C3C。

除了两个寄存器参加移位外,任何一个双寄存器逻辑移位指令的执行都同其单寄存器指
令伙伴类似。参加移位的两个寄存器为一个偶/奇对。在移位操作期间,此对寄存器被看作
就象一个单个64位寄存器一样。

3.逻辑左移双寄存器指令SLDL
逻辑左移双寄存器指令SLDL的格式为:
SLDL r1,D(B) (逻辑左移双寄存器)

指令的执行将使一个由r1(r1必须为偶数)指定的寄存器偶/奇对的内容左移。指令的操作
码为80。

4.逻辑右移双寄存器指令SRDL
逻辑右移双寄存器指令SRDL的格式为:
SRDL r1,D(B) (逻辑右移双寄存器)

指令的执行将使由r1指定的偶/奇寄存器对的内容右移。指令的操作码为8C。

逻辑移位指令对在寄存器放置一些位非常有用。例如,要编写薪水帐册程序,假定程序
使用一个非常大的表,进一步假定表中的每项含有三个数:
z 雇员编号(4位)
z 工时数(小于100)
z 工时薪额(小于$20,00)
如果项中的每个数用一个全字表示,每个项占用12字节存贮。从节省空间考虑我们应该
将所有三个数压缩在单个字中,这可以通过使用如下格式来实现:
位0-13 =雇员编号
位14-20 =工时数
位21-31 =工时薪额
这样一来,空间可以节省三分之二,但是数据的访问确因此变得复杂了。为了访问表中项的
数据,有必要让一段代码将这些数彼此分离出来,如下代码段将完成这一功能:

*假定R1包含一个表项
LR R2,R1
N R2,=X'000007FF'
*R2此时包含工时薪额
LR R3,R1
SRL R3,11
N R3,=X'0000007F'
*R3此时包含工时
LR R4,R1
SRL R4,18
*R4此时包含雇员编号

- 184 -
如下指令序列提供分离雇员编号的另一种方法:
SR R0,R0
SLDL R0,14
*注意现在R0有雇员编号,注意R1也被象R0一样移位。

除了四条逻辑移位指令外,还有四条算术移位指令,SLA、SRA、SLDA和SRDA,尽
管算术指令的移位操作类似于逻辑移位操作,它们常被采用的场合却不同,顾名思义,算术
移位用于算术运算,准确的说,用于达到2的幂的乘法和除法的效果。

5.算术左移单寄存器指令SLA
算术左移单寄存器指令SLA的格式为:
SLA r1, D(B) (算术左移单寄存器)

本指令的操作码为8B。指令的执行在如下几方面不同于SLL指令的执行:
z 在算术左移中,位0(符号位)不参与移动。如果一个与符号位不同的位移出位置1,
将产生溢出。因此,算术移位从来不改变符号位;
z 条件码CC设置如下:
CC 含义
0 结果为0
1 结果<0
2 结果>0
3 溢出
SLA的主要用途在于达到乘以2的幂的效果,正如十进制数左移1位产生将那个数乘以10
的效果一样,二进制数左移1位产生乘以2的结果,因此,如下指令:
SLA R1,4
具有如下指令对R1的内容产生的同样的效果:
M R0,=F'16'
并且指令的执行需要更少的时间。这一技术应只用于幂指数大于1的2的幂,即不用于左移1
位乘2的情形,因为使用AR指令把寄存器的内容加到其自身所花的运行时间更少。

6.算术右移寄存器指令SRA
算术右移寄存器指令SRA的格式为:
SRA r1, D(B) (算术右移寄存器)

本指令的操作码为8A。指令仅在如下几方面不同于SRL指令的执行:
z 移位空出的位不是用零填充,而是取符号位的值。即如果寄存器的内容表示一个
正数则用0填;如果表示一个负数则用1填。例如,R1的内容在如下指令执行后:
L R1,=X'FFFFFFFF'
SRA R1,2
将为 FFFFFFFF。
z 除不会产生溢出外,条件码的设定同SLA指令。
SRA指令用于达到数被2除的效果。例如,将R2除以64可以通过使用如下指令完成:
SRA R2,6

- 185 -
当使用这一技术而寄存器内容表示的数不是2的幂的倍数时,结果会产生近似值。结果
总是向下取整。因此,如下指令的执行:
LA R1,22
SRA R1,2
将R1的内容改变为00000005,而如下指令的执行:
L R1,=F'-22'
SRA R1,2
将R1的内容置为FFFFFFFA,这个例子涉及一个负数,读者应牢记这种方法的结果可能不同
于使用除法指令得到的结果。
SLDA和SRDA指令完成同指令SLA和SRA相同的操作,不同的是前面的操作应使用偶/
奇寄存器对。

7.算术左移双寄存器指令SLDA
算术左移双寄存器指令SLDA的格式为:
SLDA r1,D(B) (算术左移双寄存器)

指令的执行将在r1指定的偶/奇寄存器内容上进行算术左移操作。在所有其它的细节上,
其操作同SLA指令的操作相同。指令的操作码为8F。

8.算术右移双寄存器指令SRDA
算术右移双寄存器指令SRDA指令的格式为:
SRDA r1,D(B) (算术右移双寄存器)

指令的执行在由r1指定的偶/奇寄存器对内容上完成算术右移操作。在其它方面,该操
作同SRA指令相同。指令的操作码为8E。
程序9-1对本节进行了总结,该程序读入范围在0到3999之间的数并确定读放了多少个不
同的数。该程序维护包含4000个表项的表(每个表项记录一个二进制位)。最初,表中所有
位为0。当程序读入数后,表中相应该数的位改变为1。当移到文件结束时,确定表中位值为
1的个数并打印答案。

TITLE ’COUNTUP – COUNT THE NUMBER OF UNIQUE INTEGER'


**************************************************************************************************
* 这个程序读入一组整数,计算出不同的整数的个数,当读入一个数的时候,这个数将在
* 表格中做上标志。表的大小是 32*125 位,每位对应着 0-3,999 之间的一个值。程序
* 的逻辑步骤如下:
* 第 1 步:读第一个记录
* 第 2 步:如果结束,跳到第 5 步,计算被设为 1 的位的个数。
* 第 3 步:没有结束,把这个值对应的位设为 1。
* 第 4 步:读下一个记录,跳到第 2 步。
* 第 5 步:把指针指向表的第一个字。
* 第 6 步:如果表中所有的字都被计算,就跳到 11 步。
* 第 7 步:把字取出来进行运算。
* 第 8 步:如果字中所有的位都被计算,就跳到第 10 步。
* 第 9 步:找出字中是 1 的位进行计算,然后把它设回为 0,跳到第 8 步。

- 186 -
* 第 10 步:把指针指向下一个字,跳到第 6 步
* 第 11 步:打印结果。
* 第 12 步:退出。
***************************************************************************************************
*
COUNTER CSECT
STM R14,R12,12(R13) 保存调用程序的寄存器
BALR R12,0
USING BASEPT,R12
BASEPT DS 0H
LA R14,SAVEAREA
ST R14,8(R13)
ST R13,4(R14)
LR R13,R14
*
**<第 1 步> 读一个记录
*
XREAD CARD,80
*
**<第 2 步> 检查是否结束
*
EOFTEST BC B'0100',GOCOUNT
*
**<第 3 步> 表格中对应位设为 1
*
PACK DWORD,CARD(6)
CVB R3,DWORD
*
LA R2,31 把 0000001F 送入 R2
NR R2,R3 R2<-读入数据/32 的余数
SRA R3,5 R3<-读入数据/32 的商
SLA R3,2 商乘 4 计算出表格中的偏移量
L R4,TABLE(R3) R4<-取出要标记的数
L R5,=X'80000000'
SRL R5,0(R2) 该数中的第几位?
OR R4,R5 作标记
ST R4,TABLE(R3) 存回表格中
*
**<第 4 步> 读下一个记录,如后跳到第 2 步
*
READNXT XREAD CARD,80
B EOFTEST
*
**<第 5 步> 跳到表格的第 1 个整数

- 187 -
*
GOCOUNT LM R1,R4,=A(TABLE,0,1,125) R1<-下一个要计算的数
* R2<-计数器
* R3<-常数 1
* R4<-整数个数
*
**<第 6 步> 检查表格是否结束
*
TESTEND S R4,=F'1' 每次减掉一个整数
BM GOPRINT
*
**<第 7 步> 读下一个整数来统计
*
L R0,0(R1)
*
**<第 8 步> 该整数中还有位没有统计吗?
*
TESTFOR0 LTR R5,R0
BZ NEXTWORD
*
**<第 9 步> 取出下一位,计数,并清零
*
AR R2,R3 计数器加 1
SLR R5,R3 本指令连同下一条指令将寄存器
* 最右边的一位清零
NR R0,R5
B TESTFOR0
*
**<第 10 步> 指针指向下一个整数并转第 6 步
*
NEXTWORD LA R1,4(R1)
B TESTEND
*
**<第 11 步> 打印结果
*
GOPRINT CVD R2,DWORD
MCV ANSWER(10),=X'40206B20206B202120'
ED ANSWER(10),DWORD+4
XPINT CC,24
*
**<第 12 步> 退出
*
L R13,4(R13)
LM R14,R12,12(R13)

- 188 -
BR R14
*
LTORG
CARD DS CL80 数据输入区域
SAVEAREA DS 18F 寄存器保存区域
CC DC C'0THE ANSWER IS'
ANSWER DS CL10
DWORD DS D 用于转换
TABLE DC 125F'0' 表格
R0 EQU 0
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6
R7 EQU 7
R8 EQU 8
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END COUNTUP

程序9-1

第四节 半字指令

IBM S/390中通常处理的数据单位是全字,即32位的二进制数,但有时候我们需要处理
更小范围的数,例如16位二进制数据,亦即半字。在IBM S/390指令集中包含处理存贮于半
字中的16位有符号整数的指令,半字是开始于偶地址的两个相邻的字节。
编码32位全字有符号整数的方法可以推广到可变大小的存贮单位。尤其是,有符号整数
可以用如下方式编码于半字中:
z 任何在0到7FFF范围的数可以以其常规2进制表示并编码于半字中。因此,可以表示的
最大数为32,767。
z 任何在范围-1到-8000之间的整数n,可通过自FFFF减去|n|加1而编码。
注意,一个被编码的负数的符号位(最左位)总是为1,而正数的符号位总是为0。
半字存贮区域及常量可以通过使用DS和DC语句产生。用于半字区域的规则和用于全字
区域的规则的唯一不同之处是使用H代替F。因此,指令:

- 189 -
标号 DS nH
用于保留一个n个半字的区域(自一个偶地址开始的2n字节)。而指令:
标号 DC nH'c'
产生n个连续的包含常量c的半字。在每种情形下,如果省略n则假定其为1。作为DC语句定
义的例子,考虑如下表:

DC语句定义 产生的结果
DC H'0' 0000
DC H'1' 0001
DC H'-1' FFFF
DC H'-2' FFFE
DC 2H'12' 000C000C
DC H'32767' 7FFF
DC H'-32768' 8000

半字符号常数可以同全字符号常数相同的方式使用,在装入半字指令的讨论中我们将给
出一个示例。
由于半字整数的存贮量只需要全字整数的一半,一个程序的存贮空间需求通过使用半字
而大大节省。使用半字的限制因素是其所表示值的范围为-32768到32767。然而,这一范围
对于许多涉及整数表格的应用是足够的。以下我们讨论半字指令LH、STH、CH、AH、SH
和MH。

1.装入半字指令LH
装入半字指令LH的格式为:
LH r, D(X,B) (装入半字)
功能:(r)←(D+(X)+(B))

指令的执行用开始于地址D(X,B)的半字的相应32位数的替换r的内容。因此,在指令执
行后,若装入的值分别为正数和负数,r最左边的两个字节相应的将包含0000和FFFF。存贮
器中的半字操作数不变。本指令的操作码为48。指令执行不影响条件码CC。
例如,在指令
LH R1,=H'-2'
执行后,R1的内容将为FFFFFFFE。

2.存储半字指令STH
存储半字指令STH的格式为:
STH r, D(X,B) (存储半字)
功能:(D+(X)+(B) )←(r)

指令的执行将使D(X,B)处的半字被r的最右边的两个字节(即位16-31)所替换。r的内容
不变。本指令的操作码为40。指令执行不影响条件码CC。

3.比较半字指令CH
比较半字指令CH的格式为:

- 190 -
CH r, D(X,B) (比较半字)
功能:
(r)-(D+(X)+(B))

指令的执行将r的内容和D(X,B)处的半字进行比较。指令的操作码为49。根据比较结果,
条件码CC设置如下:
CC 含义
0 操作数相等
1 第一个操作数小
2 第一个操作数大
3 -

半字指令AH、SH和MH分别用于完成算术运算加、减和乘法。然而,没有在半字操作
数上完成除法的指令。

4.加半字指令AH
加半字指令AH的格式为:
AH r, D(X,B) (加半字)
功能:(r)←(r)+(D+(X)+(B))

指令的执行与A指令类似,唯一的区别是D(X,B)引用的是一个半字整数而非全字表示。
指令的操作码为4A,指令执行影响条件码CC。

5.减半字指令SH
减半字指令SH的格式为:
SH r, D(X,B) (减半字)
功能:(r)←(r)-(D+(X)+(B))

指令的执行与S指令类似,唯一的区别是D(X,B)引用的是一个半字整数而非全字表示。
指令的操作码为4B,指令执行影响条件码CC。

6.乘半字指令MH
乘半字指令MH的格式为:
MH r, D(X,B) (半字乘)
功能:(r)←(r)×(D+(X)+(B) )

指令的执行将r的内容和D(X,B)处的半字相乘,结果替换r的内容。溢出不会引起一个中
断。因此,程序员必须确信结果必须能以32位表示。指令的操作码为4C。

程序9-2解释了半字指令的用法。

TITLE ‘HALFWORD – COUNT OCCURRENCES OF NUMBERS’


*************************************************************************************************
* 这个程序读输入记录,记录的内容是 0--9,999 之间的整数。对应于这个范围的
* 每一个数都有一个半字计数器计算每一个数读入的次数。当读入一个数的时候,

- 191 -
* 相应的计数器会增加 1。当所有的数都读完的时候,程序会打印出含有非零值
* 的每一个计数器。程序的逻辑如下:
* 第 1 步:读第一个记录;
* 第 2 步:如果输入结束,跳到第 5 步;
* 第 3 步:把相应的计数器增 1;
* 第 4 步:读如下一个记录,然后跳到第 2 步;
* 第 5 步:打印表头,并准备好处理表格中的数据;
* 第 6 步:如果所有的计数器都检查过,就跳到第 9 步;
* 第 7 步:如果计数器非零,打印;
* 第 8 步:准备下一个计数器,跳到第 6 步
* 第 9 步:结束
*************************************************************************************************
HALFWORD CSECT
STM R14,R12,12(R13)
BALR R12,0
USIBG BASEPT,R12
BASEPT DS OR
LA R14,SAVEAREA
ST R14,8(R13)
ST R13,4(R14)
LR R13,R14
*
**<第 1 步> 读第一个记录
*
XREAD CARD,80
*
**<第 2 步> 检查是否结束
*
EOFTEST BC B'0100',PRTCNTRS
*
**<第 3 步> 把相应的计数器加 1
*
PACK DWORD,CARD(4) 压缩输入数据
CVB R2,DOWND
AR R2,R2 数据翻倍以便找到对应的计数器
* 在表格中的地址
LH R3,TABLE(R2)
LA R3,1(R3) 计数器加 1
STH R3,TABLE(R2)
*
**<第 4 步> 读下一个记录
*
XREAD CARD,80
B EOFTEST

- 192 -
*
**<第 5 步> 打印表头并将指针置于计数器表格头部
*
PRTCNTRS XPRNT =CL22'1 NUMBER OCCURRENCES',22
SR R2,R2 R2<-第一个计数器
LH R3,=H'10000' R3<-计数器总数
*
**<第 6 步> 检查表格是否结束
*
TESTEND SH R3,=H'1'
BM EXIT
*
**<第 7 步> 如果计数器>0 的,就打印
*
LA R4,0(R2,R2) R4<-计数器地址偏移量
LH R5,TABLE(R4)
LTR R5,R5
BZ INCRCNT =0
*
CVD R2,DWORD
MVC NUMBER(7),=X'4020206B202120'
ED NUMBER(7),DWORD 编辑该数
*
CVD R5,DWORD
MVC VALUE(7),=X'4020206B202020'
ED VALUE(7),DWORD+5 编辑出现次数
*
XPRNT PLINE,22
*
**<第 8 步> 跳到下一个计数器
*
INCRCNT LA R2,1(R2)
B TESTEND
*
**<第 9 步> 结束
*
EXIT L R13,4(R13)
LM R14,R12,12(R13)
BR R14
*
LTORG
DWORD DS D 用于转换
CARD DS CL80 数据输入空间
SAVEAREA DS 18F 寄存器保护区

- 193 -
PLINE DC C'0'
NUMBER DS CL7
DC CL5' '
VALUE DS CL7
DC CL2' '
TABLE DC 10000H'0' 计数器表
*
R0 EQU 0
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6
R7 EQU 7
R8 EQU 8
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END HALFWORD

程序9-2

第五节 MVCL、CLCL 和 EX 指令

MVCL和CLCL指令是前面我们介绍过的MVC和CLC指令的推广版。它们用于完成长达
24-
2 -1个字节字段的移动和比较。

1.长移动指令MVCL
长移动指令MVCL的格式为:
MVCL r1,r2 (长移动)

本指令的每个操作数必须是一个偶数寄存器以指定偶/奇寄存器对。每个寄存器对说明
长度从0到224--1个字节的字段,每个偶寄存器说明操作数的内存地址,每个奇数寄存器最
右边的三个字节说明相应字段的长度。指令的执行使得由r2说明的字段的内容被移动到r1指
定的字段中。
如果发送字段的长度小于接收字段的长度,由r2说明的奇数寄存器的最左字节表示的填
充字符插入到接收字段的剩余右边字节中。如果一个字节同处于发送和接受字段中,并且如

- 194 -
果MVCL指令的执行使这个字节在原有的内容未移走之前接受一个新的值,就称存在破坏性
重叠。在这种情况下,不产生数据移动。指令的操作码为0E。指令的执行影响条件码CC,
CC的设置如下:
CC 含义
0 发送和接收域长相等
1 发送域较长
2 接收域较长
3 破坏性重叠存在
如果数据移确实进行了(不存在破坏性重叠条件),则会发生如下事件:
1、r1指定的偶寄存器的地址设置到接受字段结束后的下一个字节的地址;
2、r1指定的奇寄存器的值设定为0;
3、r2指定的偶寄存器的值设定为发送字段送走的最后一个字节的下一个字节的地址;
4、r2指定的奇寄存器的值为发送字段送走的字节数递减后的结果。

因而,使用MVCL指令较之于MVC指令提供了以下优点:
1、可以移动任意长度的字段;
2、字段的长度可在执行时确定;
3、填充自动进行。

如下例子解释了MVCL的使用:

例1、假定R2包含一个字段地址,R3包含其长度。为了将其每个字节设置为00,可使用如下
代码:
LR R4,R2 R4<-字段地址
LR R5,R3 R5<-长度
LR R6,R2 R6必须包含有效地址,但未用
SR R7,R7 填充字符和长度均为0
MVCL R4,R6 仅填入填充字符

例2、假定一个1到6位数字长的整数已移动到主存的一个存贮区域(比方说,DUMMY),要
写一段代码将这个数移动到PLINE中,从PLINE+7处字段的左部开始存放DUMMY的非空
格字符。进一步假定数已经通过XDECO指令存放到DUMMY,在这种情况下,DUMMY是
一个12字节的字段,非空格字符存放在DUMMY字段右部。PLINE已被定义如下:
PLINE DC CL19'0TOTAL='
如下一段代码代表了该问题的一个解决方案:
LA R2,DUMMY
LA R3,12
LOOP CLI O(R2),C' ' 检查第1个非空
BNE GOTC
LA R2,1(R2)
BCT R3,LOOP
*
GOTC O R3,=X'40000000' 填充字符空格
*

- 195 -
LM R4,R5,=A(PLINE+7,12)
MVCL R4,R2
XPRNT PLINE,19

2.长逻辑比较指令CLCL
除了CLCL指令用于比较而非数据移动外,CLCL指令类似MVCL,它的格式为:
CLCL r1,r2 (长逻辑比较)

指令的执行比较主存中的两个字段,这两个字段的地址及长度以同MVCL指令描述的方
式一样。如果字段不是等长的,r2指定的寄存器对的奇数寄存器的最左字节表示的填充字符
扩展较短的字段到等长以便比较(实际上,任何存贮区域都不改变,但是就好像较短的字段
已被扩展一样进行比较) 。指令的操作码为0F。指令的执行影响条件码CC,CC设置如下:
CC 含义
0 字段相同
1 第一个字段小
2 第一个字段大
3 -
比较自左至右地进行,每次一个字节,当两个字节匹配时,地址寄存器(偶数)增1而计
数寄存器(奇数)减1。唯一的例外是当字段之一用尽时,剩下的字段中的字节同填充字符比
较,而只有相应于该字段的寄存器被改变。因此,如果两个字段不匹配,两个偶数寄存器的
内容可用于确定不同之处位置,不像MVCL两个操作数不能重叠,CLCL使用的两个操作数
可以重叠。
当字段的长度必须在执行期间确定时,MVCL和CLCL对于字段的移动和比较是很有用
的。当字段长度直到执行时才能确定时,这些指令是必要的,如压缩一个数或用TRT指令扫
描字段。在这种的情况下,要使用EX指令。

3.执行指令EX
执行指令EX的格式为:
EX r,D(X,B) (执行)

指令的执行将做如下动作:
⑴.生成D(X,B)处指令的副本;
⑵.副本的第2个字节同r的最右字节进行OR运算,结果替换该副本的第2个字节;
⑶.执行该指令副本。
r的内容和D(X,B)处的原指令均不改变,如果D(X,B)处的指令不是一条转移指令或不是
一个成功的转移的话,紧接EX指令之后的指令将是要执行的下一条指令;如果D(X,B)处的
指令是一个成功的转移,当然会进行转移。条件码由D(X,B)处的指令副本设置。在r为R0的
情况下,不进行OR操作。
注意地址D(X,B)没有限制,EX引用的指令可以在任何地方;然而,通常的做法是在程
序的尾部将它同常量放在一起。
EX指令最普遍的用法是插入一个特定的值到它访问的指令的第2个字节,这可以通过在
EX指令的第1个操作数(寄存器)的最右字节插入一个值并将EX编码访问的指令相应的第2
个字节设置为0来完成。在第2个字节只有一个十六进制数字要改变的情况下,被访问指令仅
相应的十六进制数字必须为0。

- 196 -
例如,假定R2包含一个非压缩(或字符)格式数的地址,而R3包含该数的长度。则如
下代码可用于将该数压缩至DWORD中。
SH R3,=H'1' R3现为该字段的长度编码
BM ERR 如原长度为0或负数则转移
EX R3,PACKIT
……
DWORD DS D
PACKIT PACK DWORD,0(0,R2)
*
在本书的后续章节中,将进一步介绍MVCL、CLCL和EX的使用。

第六节 TR 指令

有时我们有必要“翻译”出现在给定字段中字节的值。这种翻译总的来说就是将每个值
用一种固定对应关系指定的对应值替换。作为一个例子,假定一个字段包含一个字符串,除
了其中某些字符是以小写形式编码表示的外,其余的都是大写字母。现在我们希望在打印前
将小写字母“翻译”为大写字母。如下对应表交用于翻译:

原始字符 00 01 … 80 81 82 … 89 8A 8B … 90 91 92 … 99
替换字符 00 01 … 80 C1 C2 … C9 8A 8B … 90 D1 D2 …D9
原始字符 9A 9B … A1 A2 A3 … A9 AA AB … FF
原始字符 9A 9B … A1 E2 E3 … E9 AA AB … FF

上述图解表示中的斜体部分对应的小写字母表示要被替换的新值(即大写字母) 。因此,
通过使用上述表说明的值,串C140A2A3899587将被转换为C140E2E3C9D5C7。
由此讨论得知,一个串的转换结果依赖于翻译过程使用的对应表,对应表在内存中用一
个翻译表(或称为换码表)表示。一个翻译表是一个256字节存贮区域,这样如果ff是对应
表中aa的替换值,则ff是表中自开始位置偏移量为aa字节的值:
table+00 包含00的替换值
table+01 包含01的替换值
……
table+FE 包含FE的替换值
table+FF 包含FF的替换值
作为说明,如下TRANTAB的定义产生以上讨论的对应表的翻译表。
TRANTAB DC X'000102030405060708090A0B0C0D0E0F'
DC X'101112131415161718191A1B1C1D1E1F'
DC X'20 ……
DC X'30 ……
DC X'40 ……
DC X'50 ……
DC X'60 ……
DC X'70 ……

- 197 -
DC X'80C1C2C3C4C5C6C7C8C98A8B8C8D8E8F'
DC X'90D1D2D3D4D5D6D7D8D99A9B9C9D9E9F'
DC X'A0A1E2E3E4E5E6E7E8E9AAABACADAEAF'
DC X'B0 ……
DC X'C0 ……
DC X'D0 ……
DC X'E0 ……
DC X'F0 ……

用于完成字符串翻译的TR指令具有如下格式(SS格式):
TR D1(L,B1),D2(B2) (翻译)

该指令将长度为L开始于地址D1(B1)处的字段用开始于地址D2(B2)的翻译表(256字节)
来进行翻译。指令的操作码为DC。指令的执行不影响条件码CC。
例如,指令
TR STRING(40),TRANTAB
将根据TRANTAB表示的对应表修改字段STRING的40个字节。
程序9-3中的子程序展示了TR指令的一种不平常但却很有用的用法,该子程序可用于将
R1的内容转换成一个串,用于打印该内容的实际十六进制表示。例如,如果R1包含
FFFFFF1A,调用HEXDUMP将产生C6C6C6C6C6C6F1C1。注意TRANTAB的前240字节可以
包含任何值,因为要翻译的每个字符都将F作为非压缩十进制中的标志。因此,如下TR指令
和TRANTAB的定义同程序中的用法等效。
TR DUMMY(8),TRANTAB-C'0'
……
TRANTAB DC C'0123456789ABCDEF'
在这种情况下,DUMMY中字节的原值被加到TRANTAB-C'0'中的值上,原值总是指
向TRANTAB所希望的替换值。请仔细研究这个例子,尽管程序不长,但是它运用了相当多
的技巧。注意TRANTAB-C'0'必须出现在同TRANTAB相同CSECT中,不然的话汇编程序将
检测出错误。

TITLE ‘HEX DUMP ROUTINE’


****************************************************************************************************
* 以下的子程序把 R1 的内容转换为 8 字节的打印形式。R2 存放结果的内存首地址。
* R10 保存返回地址。本程序没有改变其它寄存器。
* 本程序的逻辑如下:
* 步骤 1:将 R1 的内容保存到 FWORD;
* 步骤 2:把 FWORD(5)解压缩到 DUMMY(9)。这就把 R1 的 16 进
* 制数放到 DUMMY 的前 8 个字节内。该区域的每个字节
* 的高 4 位都是 F。 这就意味着 R1 的数字都已分成独立的字
* 节(每个字节内有一个 F)。因此,0-9 已经是可打印形式了,
* A-F 则必须转换;
* 步骤 3:翻译 DUMMY 里的前 8 个字节。让 F0,F1,…F9 不变,以及让
* FA,FB,FC,FD,FE,FF 翻译为 C1,C2,C3,C4,C5,和 C6;
* 步骤 4:把结果从 DUMMY 移到 R2 指出的内存区域并

- 198 -
* 按 R10 记录的地址返回主程序.
*****************************************************************************************************
SPACE 3
*
***<步骤 1> 把输入寄存器中的值存入 FWORD.
*
HEXDUMP ST R1,FWORD
*
***<步骤 2> 解压缩到 16 个数字区.
*
UNPACK DUMMY(9),FWORD(5) FWORD 和 DUMMY 的最右字节
* 内容未定
*
***<步骤 3> 把数字翻译为正确的数制形式
*
TR DUMMY(9),TRANTAB
*
***<步骤 4> 移到调用程序的相应区域
*
MVC 0(8,R2),DUMMY
BR R10 退出
SPACE
*
FWORD DS F,CL1 1 个字加上 1 个字节无用空间
DUMMY DS CL9
TRANTAB DS CL(X’F0’) 这部分不使用
DC C’0123456789ABCDEF’
******HEX DUMP ROUTIN 结束******

程序9-3

第七节 TRT 指令

SS格式的TRT指令(翻译及测试)可以用于定位字符串中某个字符的首次出现的位置,
该字符是一个指定字符集的成员。TRT指令同TR指令一样,采用一个翻译表。然而,与TR
指令不同的是,TRT指令不改变第一个操作数指定的字符串。
TRT指令的格式为:
TRT D1(L,B1),D2(B2) (翻译测试)

指令中第一个操作数说明要扫描的字段的地址和长度,第2个操作数说明翻译表的地址,
本条指令的执行使指定字段的每个字节被扫描直到发现相应翻译表中非00值的字符。如果没
有找到这样的字符,条件码设置为0,如果找到了这样的字符,扫描终止且执行如下动作:

- 199 -
1、检测到的字符的地址放在R1的最右边3字节,R1的位0-7不变;
2、翻译表相应的非零项(字符)插入到R2的最右字节,R2的位0~23不变;
3、如果检测到的字符是被扫描字段的最右字节则条件码设置为2,否则设置为1。
以下例子将帮助理解TRT指令的用法:

例1、假定在称为CARD的80字节字段中要找第一个非空字符的地址,如下指令的执行:
TRT CARD(80),SCANTABL
BC B'1000',ALLBLANK
……
SCANTABL DC 64X'FF'
DC X'00'
DC 191X'FF'
将使涉及的字节的地址插入到R1的最右三个字节中,而R2最右边的字节将被设置为FF,如
果CARD中的每个字节均为空格,则转移到ALLBLANK且R1和R2均不改变。
当产生一个翻译表时,以前介绍过的ORG指令可以用来精确地说明要设置哪个表项。
例如,上面的表可以产生如下:
SCANTABL DC 256X'FF'
ORG SCANTAB+C' '
DC X'00' 跳过空格
ORG

例2、假定在CARD中要寻找第一个空格字符而不是非空字符,则可以使用如下代码:
TRT CARD(80),SCANTABL
BC B'1000',NOBLANKS
……
SCANTABL DC 256X'00'
ORG SCANTABL+C' ' ORG到空白表项处
DC X'FF' 空白处停止
ORG

例3、如果要在称为STRING的字段中查找或数字或字母的首次出现,则可以使用如下代码
SR R2,R2
TRT STRING,TRANTAB
B BRANCHES(2)
BRANCHES B NONEFND
B ACPHA
B NUMERIC
……
TRANTAB DC 256X'00'
ORG TRANTAB+C'A' 字母置为X’04’
DC 9X'04' A-I
ORG TRANTAB+C'J'
DC 9X'04' J-R
ORG TRANTAB+C'S'

- 200 -
DC 8X'04' S-Z
ORG TRANTAB+C'0'
DC 10X'08' 数字置为X’08’
ORG
在这种情况下,如果没有数字或字母找到,将会转移到NONEFND;如找到字母则会转移到
ALPHA;如找到数字则会转移到NUMERIC。特别注意R2用作对转移指令表的索引。

例4、假定要在从R3中的地址开始的字段中找出第一个'$'符号的出现位置,进一步假定要扫
描的最大字节数由R4的值表示。可以通过使用EX指令将长度编码插入到TRT指令的第二个
字节达到期望的结果,如下所示:
BCTR R4,0 长度编码
EX R4,SCAN
BC B'100',NONEFND
……
SCAN TRT 0(0,R3),TABLE
TABLE DC 256X'00'
ORG TABLE+C'$'
DC X'FF' 寻找’$’
ORG

这些例子的理解将使程序9-4中出现的外部过程的细节易于领会,这个外部过程很短,但对
本节中的概念解释得很好,值得仔细学习。

TITLE ‘GETNUM – EXTRACT A NUMBER FROM A FIELD’


**************************************************************************************************
* 本程序是将非压缩十进制数(可能带有前导空格、尾部空格、和符号位)转换为
* 二进制数。程序接受的参数表格式为:
* 十进制数的地址
* 十进制数长度的地址
* 如果转换是成功的,R15 将会被设为 0,R0 将存放二进制数值,R1 会指向数字末尾;
* 否则 R15 将设为 4。数字前或后出现的错误被忽略。
* 程序逻辑如下:
* 步骤 1: 如果长度是正值,跳转到步骤 3;
* 步骤 2: 设定一个失败返回值(4) 并跳转步骤 13;
* 步骤 3: 设定一个标志的默认值为“F”并检测十进制数的第一位;
* 步骤 4: 如果找不到符号标志或数字,跳转步骤 5,否则,你有 3 种情况(+,-,
* 或数字) ,如果找到加标志,跳转步骤 6,如果找到减标志,跳转步骤 7,
* 如果找到数字标志,跳转步骤 8;
* 步骤 5: 设定一个失败返回值(4)并跳转步骤 13;
* 步骤 6: 增加指针值并跳转步骤 8;
* 步骤 7: 设定减标志(D),并增加指针值;
* 步骤 8: 若指针仍在数字中,跳转步骤 10;
* 步骤 9: 设定一个失败返回值(4)并跳转步骤 13;
* 步骤 10:检测数字串的结束,如果还有至少一个数字,跳转步骤 12;

- 201 -
* 步骤 11:设定一个失败返回值(4)并跳转步骤 13;
* 步骤 12:转换数字为二进制并设定成功返回值(0);
* 步骤 13:结束。
****************************************************************************************************
*
GETNUM CSECT
STM R14,R12,12(R13) 保存调用程序寄存器内容
BALR R12,0
USING BASEPT,R12
BASEPT DS 0H
LA R14,SAVEAREA
ST R14,8(R13)
ST R13,4(R14)
LR R13,R14
*
LM R3,R4,0(R1) R3ÅA(十进制数)
* R4ÅA(十进制数的长度)
L R4,0(R4) R4Å十进制数的长度
*
***<步骤 1> 测试长度(R4 被设为长度代码)
*
SH R4,=H’1’
BNM SCAN
*
***<步骤 2> 设定失败寄存器并退出
*
LA R15,4
B EXIT
*
***<步骤 3> 测试符号位或数字
*
SCAN MVI SIGN,X’0F’ 默认标志是正的
SR R1,R1
SR R2,R2
EX R4,SCANTRT 测试符号位或数字
*
***<步骤 4> 测试没有符号位或数字
*
BC B’0110’,BRANCHTB-4(R2) 若有则跳转分析是什么
*
*
***<步骤 5> 设定失败返回码并退出
*
LA R15,4 设定失败返回码

- 202 -
B EXIT
*
BRANCHTB B PLUSSIGN 如果代码表是 4
B MEGSIGN 如果代码表是 8
B PASTSIGN 如果代码表是 C(数字)
*
***<步骤 6> 扫描指针加 1
*
PLUSSIGN LA R1,1(R1)
B PASTSIGN
*
***<步骤 7> 设置减号标志并把扫描指针加 1
*
MEGSIGN MVI SIGN,X’OD’
LA R1,1(R1)
*
***<步骤 8> 检查指针是否在合法范围内
*
PASTSIGN LR R5,R1 保存开始数字
AR R4,R3 R4ÅA(最后一个字节)
SR R4,R1
BNM GETEND 没问题跳转
*
***<步骤 9> 设定失败返回码并退出
*
LA R15,4
B EXIT
*
***<步骤 10> 检测开始的结束数字
*
GETEND LA R1,1(R4,R1) 将 R1 指向下一个字节
EX R4,SCANEND 扫描数字后的第一个字符
SR R1,R5 R1Å串中数字的位数
SH R1,=H’1’
BNM CONVERT 若有数字则跳转去转换
*
***<步骤 11> 设定失败返回码并退出
*
LA R15,4
B EXIT
*
***<步骤 12> 转换为二进制
*
CONVERT EX R1,PACKNUM 压缩

- 203 -
NI DWORD+7,X’FO’ 设定标志位
OC DWORD+7(1),SIGN
CVB R0,DWORD R0 包含转换后的值
LA R1,1(R1,R5) R1 指向下一个字节
SR R15,R15 设定成功返回码
***<步骤 13> 退出
*
EXIT L R13,4(R13)
L R14,12(R13)
LM R2,R12,28(R13) R15,R0,&R1 保存它们的值
BR R14
*
SCANTRT TRT 0(0,R3),TABNUM 检测数字或符号位
SCANEND TRT 0(0,R5),TABEND 检测第一位非数字
PACKNUM PACK DWORD,0(0,R5)
*
LTORG
*
SIGN DS C 标志
DWORD DS D 用于转换
SAVEAREA DS 18F 寄存器保护区
*
TABNUM DC 256X’00’
ORG TABNUM+C’+’
DC X’04’
ORG TABNUM+C’-‘
DC X’08’
ORG TABNUM+C’0’
DC 10X’0C’
ORG
TABEND DC 256X’FF’
ORG TABEND+C’0’
DC 10X’00’
ORG
*
R0 EQU 0
R1 EQU 1
R2 EQU 2
R3 EQU 3
R4 EQU 4
R5 EQU 5
R6 EQU 6
R7 EQU 7
R8 EQU 8

- 204 -
R9 EQU 9
R10 EQU 10
R11 EQU 11
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END

程序9-4

第八节 CLM、ICM 和 STCM 指令

CLM、ICM和STCM均为RS格式的指令,每条指令均涉及两个字段之间的操作。第一个
字段由一个寄存器中的字节构成,第2个字段是存贮器中的连续字节。屏蔽中的位同寄存器
中的字节一一对应,具有1值的位指定了寄存器中相应的字节。例如,如下指令的执行
CLM R2,B'0110',KEY
将R2寄存器的中间两个字节和KEY中的前两个字节进行比较,类似地,指令:
CLM R2,B'1110',KEY
将R2的前三个字节和KEY的最左边的三个字节间进行比较。

1.字符屏蔽逻辑比较指令CLM
字符屏蔽逻辑比较指令CLM的格式为:
CLM r, m, D(B) (字符屏蔽逻辑比较)

本指令的操作码为BD。指令的执行将在两个字段之间进行逻辑比较。在一个逻辑比较
中,涉及字段的内容被看成是无符号二进制数。第一个字段由前两个操作数确定。
r 说明一个寄存器
m 指定r中的要包含哪些字节的屏蔽
屏幕m应该是一个值在0到15之间的绝对表达式,要比较的字段的长度为 i ,i 是m二进制
表示中值为1的个数。参与比较的字段由D(B)地址处连续i个字节组成,条件码设置如下:
CC 含义
0 选定的字节相等或m=0
1 第一个字段小
2 第一个字段大
3 -
为了解释,假定R0包含00AC2B40而FIELD包含9F013C2F。如下表给出了三条CLM指令
及其执行后将产生的CC设置:
指令 比较长度 CC
CLM R0,B'0000',FIELD 0 0
CLM R0,B'1010'FIELD 2 1
CLM R0,B'0111',FIELD 3 2

- 205 -
在第二种情况下,基于002B和9F01的比较结果,条码码设置为1;在第三种情况下,基
于AC2B40和9F013C的比较,产生CC=2;第一种情况下,因为m=0所以条件码为0。

ICM和STCM指令是IC和STC的推广版。IC和STC能用于传送一个字节,而ICM和STCM
可用于在屏蔽m控制下传送多达4个字节。

2.屏蔽插入字符指令ICM
屏蔽插入字符指令ICM的格式为:
ICM r, m, D(B) (屏蔽插入字符)

指令的执行把主存中D(B)地址处的0~4个字节插入到寄存器r中,屏幕m表示的4位二进
制确定要改变r的哪些字节。m中值为1的位对应r中的字节接受来自主存的字节。插入自左至
右地进行。指令的操作码为BF。条件码设置如下:
CC 含义
0 所有插入字节为0或m=0
1 插入的最左字节的最左位=1
2 插入的最左字节的最左位=0
3 --
注意,指令:
ICM r, B'1111', addr
同装入指令产生的效果类似。然而,ICM传递的4个字节不必在全字边界且会设置条件码。

3.屏蔽存储字符指令STCM
屏蔽存储字符指令STCM的格式为:
STCM r, m, D(B) (屏蔽存储字符指令STCM)

指令的执行将把m指定的r中的字节存贮到D(B)地址处开始的连续主存字节中。条件码
不变。指令的操作码为BE。

第九节 SPM 指令

本书的前面,在涉及定点和十进制溢出作用的讨论中,我们曾经提到一个溢出可能引起
一个错误从而导致程序终止,也可能不一定会产生这个效果。那么,何时溢出才会导致程序
执行的终止?答案相当简单,如果程序状态字PSW的第36位的值为1的话,程序将会终止;
否则的话,程序在出现溢出后会继续执行。位36是PSW中程序屏蔽的一部分。Bit37~39形
成程序屏蔽的其余部分,用于确定在其它异常情况下要采取的动作。
那么,这个屏蔽可以由程序员改变吗?答案是可行的,SPM指令即用于此目的。SPM
指令的格式如下:
SPM r (设置程序屏蔽)

这条指令的执行使当前PSW的位34~39被r的位2~7所替换。因此,条件码CC和程序屏蔽
均可以被改变。指令的操作码为04。

- 206 -
为了理解SPM指令的基本用法,请读者回忆执行在执行BAL和BALR指令时用PSW的整
个右半部分替换连接寄存器的内容,因此,在执行BAL和BALR指令后,连接寄存器的位2
-7包含了条件码和程序屏蔽。因此,假定连接寄存器的内容还没有被改变的话,条码码和
程序屏蔽还可以恢复到BAL和BALR执行前所具有的值,这种恢复可以通过使用以接接寄存
器作为其操作数的SPM指令完成。
改变条件码和程序屏蔽而后恢复至其原值的技术可以概括如下:
1、为保存程序屏蔽和CC,使用以下指令:
BALR R0,R0
2、输入一条SPM指令改变程序屏蔽和CC至任何期望的值。
3、在要恢复程序屏蔽和CC的地方,输入指令
SPM R0 (当然,假定R0的值未被改变)
当采用公用子程序时,这一技术经常被采用。这一技术的使用允许调用子程序保证不改
变条件码和程序屏蔽。

习题九

1、给出一个产生256字节包含如下连续16字节存贮区域的DC语句:
0F0E0D0C0B0A09080706050403020100
2、假定字节中的位从左边开始编号,从1开始。我们有兴趣产生一个256字节区域,其中最
左边的字节对应一个256字节表的00字节,第二个字节对应一个256字节表的01字节等
等。区域中的每个字节希望为表相应字节最左边位为1的编号。因此,区域将开始如下:
00 表00字节无值为1的位
08 表01字节只有第8位为1
07 表02字节只有第7位为1
07 表03字节第7、8位均为1
……
01 表FD字节有位1,2,3,4,5,7和8的值为1
01 表FE字节有位为1,2,3,4,5,6和7的值为1
01 表FF字节所有位为1
给出一系列DC语句以产生希望的256字节区域。
3、如下DS语句将保留多少内存?
DS 2D'3',B'1101',CL4'1',H'2'
给出保留的存贮区域的内容。
4、给出一条使R3中所有奇数位值置零的指令(最右边的位为位0)
5、如果R6中23-28中任意一位被置为1,给出几条指令将产生到INTERRUPT的转移。
6、给出一条将FLAGS第6位设为1的指令
7、给出一条将FLAGS第5位翻转的指令。
8、给出一个指令序列,如果FLAGS的位4或位6为1(但仅一个为1)则转到SOMEONE,如果
均为1则转到ALL,如果都为0则转到NEITHER。
9、给出一个指令序列,如果FLAG的4-7位中任意一位(或全部)为1则转到HIGH,如果位2
和位3均为1则转到MID,否则转到LOW。
10、给出指令序列把R3的位14-17放置到R4最右边(R4所有其它位设置为0),R3不变。

- 207 -
11、假定你想把WORD的内容装入R5,如WORD代表一个负数将R4置为FFFFFFFF(如果
R5表示一个非负整数将R4置为0)。给出一个只有两条指令的序列完成这一任务。
12、如下指令产生什么?
DC F'13',H'2',F'6'
13、如下指令有何不同?
DC H'10'
DC AL2(10)
14、使用如下指令有何不同?
LH R1,=X’00A0’
LH R1,=H'10
15、如下指令产生什么?
DC H'-2,3,-5'
16、给出用于字符串比较的指令,字符串的长度不必一样。如果它们不同样长,就处理为
好像在其右添加空格。更具体地说,假定处理如下域:
S1 DS A 第一个字符串地址
LEN1 DS H 第一个字符串长度
S2 DS A 第二个字符串地址
LEN2 DS H 第二个字符串长度
产生一个到HI的转移如第一个字符串高,到LOW如果第一个字符串低,到EQ如果它们
相同。
17、假定R4在其最右边4位(其它位为0)包含一个转移屏蔽。如果CC置一个和转移屏蔽某位
匹配的值,你想产生一个转移到GO,如何实现该目标?
18、考虑“将字符串的字符翻转”的问题。构造指令序列把如下字符串
ABCDEFGHIJKLMNOP
转换到
PONMLKJIHGFEDCBA
很容易通过建一个小循环完成这一工作。然而,该工作可以不必用循环完成。你能否想
出来?
19、假定LITTLE是一个字节的域,该域被看作以2的补码编码的数。给出一个指令序列使用
R8装入该数(问题是要在寄存器的左边三个字节传播符号位)。这可以用两条指令实现。
20、在程序8-1中使用一个6指令循环对一个字中的位计数,循环体对每个字中位为1的位
执行一次。因此,对于“稀疏”字,它执行的相当快。在许多位被设置的情况下,有
更快的途径。为了介绍,我们考虑在8位字节中确定1的位数的问题,考虑如下特定字
节:
00101110
第一步将8位看作8个一位的域,每个域计录了子字中1的位数(每个子字在此情况下为1
位)。因此对8位成对求和产生。
00 01 10 01
即我们现有4个计数器,其和是字中位为1的位数。将上述计数成对求和,我们得到
0001 0011
最后我们将这两个对相加得到00000100。定为最初字中位为1的位数。这样的成对求和
如何完成?让我们考虑第一步(对原始位成对求和)。可以按如下方法完成:
a. 对原始字做个拷贝,将其中一个右移一位。得到
00101110和00010111

- 208 -
b. 使用AND指令将位对最左边的位置为0,得到
00000100和00010101
c. 将两个数相加,得到
00011001
这正是我们希望得到的8位成对求和的结果。从二位计数产生4位计数可以如下完成:
拷贝上述8位结果,对其中每个副本右移两位(2位计数的大小)。每隔两位对两个副本
两位清零,将两个副本相加。
你可以认为这一过程就象取偶数个计数器,将其中偶计数器同奇计数器对齐,然后相加。
基于该技术写一子程序对一个表中的全字快速统计位为1的位数。子程序应以如下参
数作为输入全字表的地址:
z 全字表的地址
z 包含全字个数的半字地址。
它应在R0中返回表中位为1的位数。写一个程序读入一张表,调用该子程序,打印表及
最终结果。
21、有时在操作系统中使用“位图”记录哪些磁盘节没有使用,即维护一张大的位表,其
中每位表示对应于磁盘的节是否可用。在这种情况下,找出表中第一个置为1的位置
变得非常重要(即找出第一个非零字节的地址及该字节中第1个置为1的位)。这一操作
在文件管理软件中偶而是一个瓶颈。因此,执行速度快是很重要的。
写一个子程序取这样一张表并返回第一个可用位的位置,参数具体如下
z 位图的地址
z 以字节数表示长度的半字的地址
该子程序将在R0中返回第一个非零位的位数,因此,如果表包含
00 00 00 B3
答案将是(R0)=24(从左开始计数,最左边的位为位0)。程序读入一个位图,调用该子
程序,打印位图及返回的结果。注意,如果子程序写得好的话,一条TRT指令就可以
定位第一个非零字节及其第一个置为1的位。
22 、 程 序 9-5 是 一 个 产 生 随 机 数 的 过 程 。 使 用 的 方 法 在 W.H.Payne, J.R.Rabung 以 及
T.P.Bogyo出现在ACM通讯的文章“编写Lehmen伪随机数发生器”。做这个作业无需
领会那篇文章的实质,要理解的是那个过程可以被重复调用以产生一系列在闭区内一
致地分布的随机数。

TITLE 'RANDOM -A PSEUDO-RANDOM NUMBER GENERATOR'


*****************************************************************************************************
*
* THE FOLLOWING RANDOM NUMBER GENERATOR RETURNS A VALUE BETWEEN
* TWO SPECIFIED VALUES. THE PARAMETER LIST IS AS FOLLOWS:
* ADDRESS OF A WORD CONTAINING THE MINIMUM ACCEPTABLE VALUE
* ADDRESS OF A WORD CONTAINING THE MAXIMUM ACCEPTABLE VALUE
* ADDRESS OF A WORD THAT CONTAINS THE "SEED" VALUE ON THE
* FIRST CALL. IT IS ALTERED BY EACH CALL, SO THAT A
* NEW RANDOM NUMBER IS RETURNED.
* THE ANSWER IS RETURNED IN RO (THIS IS A COMMON PRACTICE WHEN
* CODING ROUTINES THAT RETURN A SINGLE WORD). r14 MUST HAVE BEEN
* SET BY A BRANCH-AND-LINK OPERATION ( SINCE THE CALLER'S RROGRAM

- 209 -
* MASK IS ASSUMED TO BE IN r14); NORMAL LINKAGE CONDITIONS WILL
* ALWAYS CAUSE r14 TO CONTAIN THE CALLER'S PROGRAM MASK.
* THE SEED VALUE CAN BE ANYTHING ON THE FIRST CALL. NOTE THAT
* THE ORIGINAL VALUE OF THE SEED DETERMINES THE SEQUENCE OF RANDOM
* NUMBERS THAT WILL BE PRODUCED.
*
*****************************************************************************************************
*
RANDOM CSECT
STM R14,R12,12(R13) STORE CALLERS REGISTERS
BALR R12,0
USING RANBASE,R12
RANBASE DS 0H
*
LM R2,R4,0(R1) R2 <-ADDRESS OF THE MINIMUM VALUE
* R3 <-ADDRESS OF THE MAXIMUM VALUE
* R4 <-ADDRESS OF X(N) :X(O) IS THE
* "SEED" VALUE
L R5,0(R3) R5 <-MAX VALUE
S R5,0(R2) R5 <-MAX VALUE -MIN VALUE
LA R5,1(R5) R5 <-(MAX -MIN) +1
AR R5,R5 R5 <-2# (MAX-MIN +1)
*
L R1,=F'16807' R1 <-MULTIPLIER K
SR R0,R0
SPM R0 IGNORE OVERFLOW
M R0,0(R4) K*X(N)
SLDA R0,1 I SOLATE Q(N)
SRL R1,11 ISOLATE R(N)
AR R1,R0 ADD Q(N) TO R(N)
BNO NOVERFLW
OVERFLOW SL R1,=X'7FFFFFFF'
NOVERFLW ST R1,0(R4) STORE X(N+1)
*
MR R0,R5
A R0,0(R2)
*
LM R14,R15,12(R13) RESTORE CALLER'S REGISTERS
LM R1,R12,24(13)
SPM R14 RESTORE CALLER'S PROGRAM MASK
BR R14
LTORG

- 210 -
程序8-6将在本作业中采用,程序的输入数据将由①一个包含正数X0的单个记录,
该正数将作为随机数发生器的种子,紧接(2)一系列记录、每个记录在其第1列或包含0
或包含1,接一个正整数N。如果在第1列中的值为1,该记录还应包含另外两个表示闭
区间下限和上限的数。
在设置开始值为X0后,程序根据第1列的值以如下两种方式之一应处理其余记录。
程序遇到end-of-file结束。
如果第1列包含0,简单地在1-1000闭区间内产生随机数,用一个合适的标题每行
打印10个随机数。
如果第1列为1,通过记录中给出的两个数定义的闭区间产生随机数。在这种情况
下,不打印产生的随机数,使用分布表计录不同值出现的次数。当N个表项的每项至少
有一个随机数产生时,以如下样板格式打印结果:
93 **
94 ***
95 ***
96 **
97 ***
98 *
99 **
100 ****
101 **
102 ***
每行中星号的数量表示相当值的个数。分布表中的每项应是一个半字。可假定100
个半字的表足够了,注意你的程序可以很容易地确定何时一个表项包含非零值,通过对
表项零值改变为1的次数计数。当计数达到范围内的元素数日时,打印分布表的内容。
23、Soundex系统用于将名字转换为4个字符代码,它有一个有用的性质即大部分发音相同
的名字转换为同样的代码。因此,赋于SMITH和SMYTHE同样的4字符代码。这对必须
通过名字查找记录的算法是有用的。通过使用如下步骤完成转换。
(1)保留名称的第一个字母,去掉所有其后出现的a,e,h,i,o,u,w或y。
(2)除第一个字母外,对保留下来的所有字母指定一个数字如下:
b,f,p,v →1
c,g,j,k,q,s,x,z →2
d,t →3
l →4
m,n →5
r →6
(3)如果两个以上的字母连续出现在原始名字里(在步骤1以前)删除所有其它的但保留第
一次出现。
(4)如果结果少于4个字符,在右边添0,如结果过长,将右部的字符去掉。
因此,SMYTHE将被转换如下:
1、YHE删除,留下SMT;
2、SMT转换为S53;
3、因在原始名中没有接连出现的字母,这一步不删除字母;
4、因S53小于4个字符,在其后添零后为S530。写一个程序读入R,每卡包含
一个自第一列开始的名字,程序要打印出该名字及该名的Soundex值。

- 211 -
第十章 宏及条件汇编

第一节 符号参数和宏

1.宏的定义及其与子程序的比较
宏语言是对基本汇编语言的扩展,这种语言提供了一种汇编时产生任意次数常用汇编语
言指令序列的机制。在一个宏定义中,指令序列仅说明一次;然后,在任何要生成这些指令
的地方,一条宏调用指令就足够了,宏的使用带来以下的好处:
⑴.代码可以复用,从而大大简化程序编码;
⑵.程序逻辑可读性提高;
⑶.有效减少程序设计错误的数量;
⑷.保证公用功能由标准过程实现。
读者可能已经注意到上述优点也是子程序所具有的。那么,子程序与宏之间有什么不同
之处吗?就上述优点而言,两者确实没有什么差别,然而从技术角度考虑,二者是有所不同
的:
⑴.子程序代码无论被调用多少次都只有一份,而宏被调用多少次就被复制多少份,
所以从空间开销而言,子程序优于宏;
⑵.子程序在运行时调用,并需要保存、恢复现场和传递参数,而宏在汇编时就已扩
展到源程序中,在运行时不需要再调用,也不需要保存恢复现场和传递参数,因此从时
间开销而言,宏优于子程序;
⑶.子程序的参数传递方式较为简单,而宏的参数传递方式丰富而复杂。
综上所述,读者应该根据实际的需要选择采用子程序或是宏。

2.符号参数
对变量符号尤其是符号参数的介绍对于宏的讨论是一个必要的前提。
变量符号(不同于通常的符号或标号)是可以被程序员或汇编程序赋不同值的符号。当根
据汇编定义产生汇编语言指令时,变量符号被赋给他们的值所替换。变量符号有三种类型:
符号参数、系统变量和SET变量,每种变量符号服务于不同功能,本节仅讨论符号参数的功
能。
变量符号的形成规则如下:
⑴.一个变量符号由2到8个字符组成,第一个字符必须为&;
⑵.第2个字符必须为字母,剩下的字符如存在必须为字母或数字。
因此:
&VARLEN &GEN2
&TABSIZE &RIOPARM
&SORT1 &LIST1
均是有效的变量符号。
符号参数用于宏定义,由程序员在引用宏的宏指令(即宏调用)中指定其值。

3.宏定义及宏调用
一个宏定义由如下项按给定顺序组成:
⑴.一个仅由操作符MACRO组成的头;

- 212 -
⑵.一个原型语句;
⑶.一序列模式语句构成的宏定义体;
⑷.单个MEND符号构成的结束。
原型语句不必有标号。但是如果有的话标号必须为符号参数。操作域必须包括一个助记
操作码,该操作码必须出现在引用宏定义的宏指令中。操作码可以包含0到200个符号参数,
由逗号分隔用以传递参数到宏,如下是宏原型语句的例子:
&LABEL EXIT
&LABEL INVOKE &NAME
&LABEL PARSE &LEN, &ADDR, &PARMTAB, &NUM
宏体可以包含两种注解,在第1列包含’*’的注释将在宏调用的时候扩展到所产生的汇编
指令序列中,第1列为’.’第2列为’*’的注释不会生成于宏调用产生的汇编指令序列中,一个
宏定义的简单例子如下所示:

MACRO
&LABEL EXIT
·*
·* 这个宏产生用于从模块返回的代码,遵循通常的连接约定。
·*
* 注,这个宏产生从模块退出的代码,该模块已为更低级
* 的调用建立了寄存器保护区,它不能应用于从最低级的
* 子程序返回(通常这种子程序不建立寄存器保存区)
& LABEL L R13,4(R13)
LM R14,R12,12(R13)
BR R14
MEND

宏指令可以有任何有效标号,也可以没有标号。操作码域必须包含宏定义的助记操作码,
操作数域可以包含用逗号隔开的0到200个操作数。当生成特定宏指令的汇编指令时,这些位
置参数替换宏定义中符号参数的值。作为例子,宏指令:

LEAVE EXIT

可用于调用先前的宏定义,在本情形下,产生的指令序列将为:

* 注,这个宏产生从模块退出的代码,该模块已为更低级
* 的调用建立了寄存器保护区,它不能应用于从最低级的
* 子程序返回(通常这种子程序不建立寄存器保存区)
LEAVE L R13,4(R13)
LM R14,R12,12(R13)
BR R14

注意在生成的指令中,宏定义的符号参数&LABEL被宏指令中的LEAVE替换。如果宏
指令中没有标号,&LABEL将被空串替换,还要注意在生成的指令中,紧挨指令的注释再次
生成,这些注解是’*’在第1列的注释,而以’.*’开始的注释没有生成。

- 213 -
作为第2个例子,考虑如下宏定义,该宏可以用于生成对外部子程序的调用:

MACRO
&LABEL INVOKE &NAME
&LABEL L R15,=V(&NAME)
BALR R14,R15
MEND

在这种情况下,宏指令或宏调用:

INVOKE SUBRTN

将生成如下代码:

L R15,=V(SUBRTN)
BALR R14,R15

这里,宏定义中的符号参数&NAME被操作数域的SUBRTN所替换。符号参数 &LABEL
被空串替换,因为宏指令没有标号。
当源程序包含宏定义时,宏定义必须先于所有汇编语言指令。然而,标题及注释可以先
于宏定义。在汇编产生的汇编清单中,每个宏指令后面紧接的是宏扩展所产生的汇编语言语
句。当然,汇编产生的目标模块同此清单中的代码相对应。
读者必须掌握一个重要概念:当汇编程序处理一个宏指令时,汇编语言代码是根据嵌在
宏定义中的说明生成的,目标代码仅当汇编程序处理汇编语言指令时生成。为了理解源文件
如何包含宏定义以及指令是如何处理的,把汇编处理过程考虑成两个不同的处理过程是很有
用的:宏处理和汇编代码生成处理。实际过程如图10-1所示:
汇编程序

宏扩展后的源代码
宏处理器
源程序

目标程序 汇编语言代
码处理器

汇编清单

- 214 -
图10-1 具有宏调用的汇编源程序处理过程

程序10-1是一个包含宏定义的本节源文件例子的清单。
程序10-2是这个源文件产生的汇编清单。注意,在汇编清单中,宏指令产生的每条语句
用一个出现在其左部的正号+区分。这个清单应在继续阅读之前同出现在本节中的材料一起
仔细研究

MACRO
&LABEL EXIT
.*
.* 这个宏产生用于从模块返回的代码,遵循通常的连接约定。
.*
* 注,这个宏产生从模块退出的代码,该模块已为更低级
* 的调用建立了寄存器保护区,它不能应用于从最低级的
* 子程序返回(通常这种子程序不建立寄存器保存区)
& LABEL L R13,4(R13) 取得调用程序的保存区地址
LM R14,R12,12(R13) 恢复调用程序的寄存器
BR R14
MEND
MACRO
&LABEL INVOKE &NAME
.*
.* 这个宏被用来调用一个外部子程序。
.* 这个被调用程序的名字应该作为第一个操作数给出
.*
&LABEL L R15,=V(&NAME) 装载被调用的子程序地址
BALR R14,R15 调用子程序
MEND
MAIN CSECT
STM R14,R12,12(R13) 保存调用程序的寄存器
BAL R14,80(R15) 现在设置 R13 作为基址寄存器
DS 18F 寄存器的保护区
ST R13,4(R14) 储存后向指针
ST R14,8(R13) 存储前向指针
LR R13,R14 R13 作为基址寄存器
USING MAIN+8,R13
LM R2,R3,=A(TABLE,10) 为调用’SORT’装载参数
INVOKE SORT 调用 SORT 子程序
LEAVE EXIT
LTORG
TABEL DC F’4,-10,9,43,32,-8,12,0,1,64’
CARD DS CL80 输入数据空间
R2 EQU 2
R3 EQU 3

- 215 -
R12 EQU 12
R13 EQU 13
R14 EQU 14
R15 EQU 15
END

程序10-1

STMT SOURCE STATEMENT


1 MACRO
2 &LABEL EXIT
3 .*
4 .* 这个宏产生用于从模块返回的代码,遵循通常的连接约定。
5 .*
6 * 注,这个宏产生从模块退出的代码,该模块已为更低级
7 * 的调用建立了寄存器保护区,它不能应用于从最低级的
8 * 子程序返回(通常这种子程序不建立寄存器保存区)
9 & LABEL L R13,4(R13) 取得调用程序的保存区地址
10 LM R14,R12,12(R13) 恢复调用程序的寄存器
11 BR R14
12 MEND
13 MACRO
14 &LABEL INVOKE &NAME
15 .*
16 .* 这个宏被用来调用一个外部子程序。
17 .* 这个被调用程序的名字应该作为第一个操作数给出
18 .*
19 &LABEL L R15,=V(&NAME) 装载被调用的子程序地址
20 BALR R14,R15 调用子程序
21 MEND
22 MAIN CSECT
23 STM R14,R12,12(R13) 保存调用程序的寄存器
24 BAL R14,80(R15) 现在设置 R13 作为基址寄存器
25 DS 18F 寄存器的保护区
26 ST R13,4(R14) 储存后向指针
27 ST R14,8(R13) 存储前向指针
28 LR R13,R14 R13 作为基址寄存器
29 USING MAIN+8,R13
30 LM R2,R3,=A(TABLE,10) 为调用’SORT’装载参数
31 INVOKE SORT 调用 SORT 子程序
32+ L R15,=V(SORT)
33+ BALR R14,R15
34 LEAVE EXIT
35+* 注,这个宏产生从模块退出的代码,该模块已为更低级

- 216 -
36+* 的调用建立了寄存器保护区,它不能应用于从最低级的
37+* 子程序返回(通常这种子程序不建立寄存器保存区)
38+ LEAVE L R13,4(R13) 取得调用程序的保存区地址
39+ LM R14,R12,12(R13) 恢复调用程序的寄存器
40+ BR R14
41 LTORG
42 =A(TABLE,10)
43 =V(SORT)
44 TABEL DC F’4,-10,9,43,32,-8,12,0,1,64’
45 CARD DS CL80 输入数据空间
46 R2 EQU 2
47 R3 EQU 3
48 R12 EQU 12
49 R13 EQU 13
50 R14 EQU 14
51 R15 EQU 15
52 END

程序10-2

第二节 条件汇编

条件汇编指令提供了改变汇编程序处理源程序语句顺序的机制。同计算机在执行程序时
转移和循环一样,它们事实上提供了提示汇编程序于汇编时在这些指令中转移和循环的手
段。尽管条件汇编语句可以用在源程序的其它部分,但是在宏定义中使用较为普遍,本节将
对此讨论。
在讨论条件汇编之前必须给出几个定义。
A.算术表达式
算术表达式可由以下元素形成:
z 自定义项
z 操作符+,-,*和/
z 变量符号
z 指示成组运算的括号
B.逻辑表达式
逻辑表达式可由以下元素组成:
z 有或没有括号的0和1
z 算术表达式由关系运算符分隔且括在括号内,有效的关系操作符如下:
EQ 相等
NE 不等
LT 小于
GT 大于
LE 小于等于

- 217 -
GE 大于等于
当计算这样一个逻辑表达式时,如果给出的关系满足则其值为1,否则其值为
0。
z 0到255个字符组成并用’’括起来的字符串,以及用撇号括起的符号参数,参数
间用关系运算符分隔并括在括号中。当计算这样的表达式时,字符串同符号参
数的值比较,如果两个串满足给定关系,则其值为1,否则为0。
C.顺序符号
一个顺序符号根据如下规则形成:
z 由2到8个字符构成,其中第一个是句号·
z 第2个字符必须为字母,其余字符如果有的话全为字母或数字。
顺序符号用在语句的名字域,并且是唯一可用在条件分支指令操作数域的名字。

1.AIF
AIF是条件汇编、条件转移指令,该指令具有如下格式。
顺序符号 AIF (逻辑表达式)顺序符号

AIF的名字域是可选的,即第一个顺序符号是可选的。当汇编处理这条指令时,将计算
操作数域的逻辑表达式。如果其值为1,则转移到AIF操作数域中以顺序符号为名的语句;
否则,紧接将处理该AIF后的语句。

2.AGO
AGO是条件汇编、无条件转移指令。该指令的格式为:
顺序符号 AGO 顺序符号

如同AIF指令一样,名字域是可选的。当汇编处理该指令时,将无条件转移到以顺序符
号为名的语句。

3.ANOP
ANOP指令提供了一种转移到名字域有符号或变量符号的语句(前)的手段,这是一个必
要的机制,因为在AIF和AGO中引用的语句必须以顺序符号命名。这条指令的格式为:
顺序符号 ANOP

当转移到ANOP指令时,其效果同转移到紧接ANOP指令之后的指令相同。
记住AIF、AGO和ANOP指令不产生代码是重要的,这些指令的唯一功能是有条件地改变汇
编处理源程序或宏定义语句的顺序。
如下例子解释了AIF、AGO和ANOP以及顺序符号的使用:

MACRO
&LABEL ROUND & WAY, & REG
.* 这个宏产生将&REG中的地址近似到全字边界的
.* 代码。如果&WAY是UP,则结果向上近似,如
.* 果为DOWN则向下近似。
AIF ('&WAY' EQ 'UP') .UP
AIF ('&WAY' NE 'DOWN') .END

- 218 -
&LABEL N &REG, =X'FFFFFFFC'
AGO .END
.UP ANOP
&LABEL LA &REG,3(&REG)
N &REG,=X'FFFFFFFC'
.END MEND

如果该宏定义被如下宏指令调用:
ALIGN ROUND UP,2
将产生如下代码:
ALIGN LA 2,3(2)
N 2,=X'FFFFFFFC'
在这种情况下,第一条AIF指令转移到有顺序符号.UP的ANOP指令的前面。因ANOP
仅用于建立转移点,相应于两条模式指令的代码被生成。注意AIF、AGO和ANOP指令不产
生任何代码,只确定宏处理器处理宏定义中语句的顺序。还要注意,.END用作MEND指令
的顺序符号。
如下另一个可替代的ROUND宏定义比先前的那个定义更简洁。

MACRO
&LABEL ROUND &WAY,&REG
&LABEL DS 0H 产生一个标号
AIF ('&WAY' EQ 'DOWN') .AND
AIF ('&WAY' NE 'UP') .END
LA &REG,3(&REG)
.AND N &REG,=X'FFFFFFFC'
.END MEND

只要分隔参数的逗号合适地放置以指示省略了哪些参数,宏指令的操作数域中的参数可
以省略。操作数域中两个连续的逗号表示参数表内一个参数的省略,前导逗号和终结逗号分
别表示参数表开始和结束处的参数省略,如果省略了一个参数,相应的符号参数被指定为空
串。
宏定义中模式语句的符号参数可以立即地衔接(或前添)其它字符(或其它符号参数)。当
出现这种情况时,其它字符(或指定给其它符号参数的字符)同指定给目前符号参数的字符相
结合,这样一种字符结合称为连接。只有一个限制:如果一个符号参数要同这样一个字符串
结合,该串用一个字母、一个左括号或句点开始,则必须立即紧接符号参数其后插入一个句
点,后接一个句点的符号参数将被指定给该符号参数的值替换。因此,在这种情况下句点的
多余使用将不会产生害处。
如下例子将用于解释前面讨论中的某些想法。

MACRO
&LABEL BHLE &REG1, &REG2,&HIGH,&LOW,&EQ
.*该宏可用于比较&REG1和&REG2中的值,产生一个到&HIGH,
.*&LOW和&EQ的转移,这要依赖于寄存器的值。
.*可以省略参数&HIGH,&LOW和EQ的任何结合形式。

- 219 -
&LABEL CR R&REG1,R&REG2
AIF ('&HIGH' EQ ' ') .LOW
BH &HIGH
.LOW AIF ('&LOW' EQ ' ') .EQ
BL &LOW
.EQ AIF ('&EQ' EQ ' ') .END
BE &EQ
.END MEND

如果宏BHLE由如下宏指令调用:

COMPARE BHLE 3,4, ,LOWVAL,EQUAL

将产生如下代码:

COMPARE CR R3,R4
BL LOWAVL
BE EQVAL

注意:CR指令操作数域中的R&REG1已被R3替换,这是一个连接例子。也要注意,在第一
条AIF指令中, &HIGH同空串比较;且因相应于&HIGH的参数被省略,转移到.LOW的分
支。
到目前为止,读者对于符号参数和赋给该类型变量符号的方式应十分熟悉。这里将介绍
第2种变量符号--系统变量符号。系统变量符号由汇编自动赋值。在本节中,只讨论系统变
量符号&SYSNDX。
系统变量符号&SYSNDX可以为从同样模式语句产生的任意数目的语句创建唯一名字。
这可以通过连接&SYSNDX和模式语句名字域中的其它字符完成。然后,当其中出现模式语
句的宏被调用时,这些字符及系统赋给&SYSNDX的值成为模式语句产生的语句的标号。
系统赋给&SYSNDX的值是一个4位数字的数。程序第一次遇到宏指令或宏调用时,
&SYSNDX的值赋0001。次后,该宏指令每遇到一次,&SYSNDX的值就增1。如下例子介
绍了&SYSNDX是怎样用的。

MACRO
&LABEL INVOKE &NAME
&LABEL STM R14,R15,IN&SYSNDX
L R15,=V(&NAME)
BALR R14,R15
LM R14,R15,IN&SYSNDX
B LV&SYSNDX
IN&SYSNDX DS 2F
LV&SYNDX DS 0H 为指令产生标号
MEND

如果宏INVOKE在程序中的某处被宏指令:

- 220 -
INVOKE READ

调用,如果这是要被处理的第23条宏指令,将产生如下代码:

STM R14,R15,IN0023
L R15,=V(READ)
BALR R14,R15
LM R14,R15
B LV0023
IN0023 DS 2F
LV0023 DS 0H

注意在程序中,调用宏INVOKE的宏指令将不会产生同样的标号。

习题十

1、有时,需要计算大于已知值的第一个2的幂,写一个宏NEXTP2
&LABLE NEXTP2 & REG
将R0设置为大于寄存器中值的第一个2的幂,例如
FINDNUM NEXTP2 R10
将产生:
FINDNUM LA R0,1
BNL *+10
AR R0,R0
B *-8
注意这段代码在循环中包含4条指令,你能否减少循环中指令的数目?你能否很容易地
产生宏以能计算任何数的第一个幂?例如,让
NEXTP R10,5
把R0设置到大于等于R0的第一个5的幂。
2、有时有必要从一个域到另一个域移动多达255字节的数据。普遍地,移动的长度只有到
执行时才知道。使用IBM 370,你可以简单地用MVCL指令完成所要的操作。在IBM 360
中不存在这样的指令,编写一个宏BIGMVC以便在IBM 360上调用。BIGMVC的前两个操
作数应指明接受和发送域的地址。第3个操作数应指明一个包含要移动的字节数的寄存器。
例如,
MOVEBIG MVC FIELD1,FIELD2,R8
应产生类似于如下的指令:
MOVE STM R14,R1,SAVR0023
LR R0,R8
LA R14,FIELD1
LA R15,FIELD2

- 221 -
LA R1,255
LP0023 CR R0,R1
BH *+8
LR R1,R0
BCTR R1,0
EX R1,MOV0023
LA R14,1(R1,R14)
LA R15,1(R1,R15)
SH R0,=h'256'
BP LP0023
LM R14,R1,SAVR0023
B SAVR0023+16
MOV0023 MVC 0(0,R14),0(R15)
SAVR0023 DS 4F
3、在4.7节中,给出了加、减、比较两个字的定点整数指令。写宏产生合适的代码,原型指
令应如下:
&LABEL ADDL &DWD1,&DWD2
&LABEL SUBL &DWD1,&DWD2
&LABEL COMP &DWD1,&DWD2
因此
ADDL NUM1,NUM2
将产生类似于如下的指令
STM R0,R1,SAVR0004
LM R0,R1,NUM1
AL R1,NUM2+4
BC B'1100',*+8
A R0,=F'1'
A R0,NUM2
STM R0,R1,NUM1
LM R0,R1,SAVR0004
B SAVR0004+8
SAVR0004 DS 2F
4、写一个宏INCR可用对内存中的一个全字增1,INCR的原型应为
&LABEL INCR &ADDR,&INCR
其中,&ADDR 存贮中的WORD的地址
&INCR 指明增量的十进制数。
宏产生的代码可以毁掉R0的内容。
5、写一个称为GETBIFS的宏,以从一个寄存器中提取一定范围的位,并将它们存放于第2
个寄存器的最右边。例如,
LAB GETBITS R4,3,10,R10
将从R4中提出位3-10,将他们放在R10最右边的位中,即产生类似如下的代码。
LAB LR R10,R4
SLL R10,21
SRL R10,24

- 222 -
6、修改COVNT和GENTAB以便COUNTERS在表第一个字包括表的项数。写一个具有如下
原型语句的宏:
& NAME TPRNT &TABLE
宏应产生一个对子程序TABPRINT的调用,传送一个仅包含要显示的表地址的参数表。
TABPRINT应产生如下输出:
THE TABLE CONTAINS n ENTRIES
COUNTER1=C1
COUNTER2=C2
.
.
COUNTERn=Cn
因此:
TPRNT COUNTERS
将把COUNTERS中的表项打印出来,该宏仅可修改R14,R15和R1。

- 223 -
附录A

扩展助记符表

扩展助记符 含义 相应的 BC 或 BCR 指令

B address 无条件转移 BC B’1111’,address


BR r 无条件转移 BCR B’1111’,r
NOP address 无操作 BC B’0000’, address
NOPR r 无操作 BCR B’0000’,r
以下指令用于比较指令之后
BH address 大于转移 BC B’0010’, address
BL address 小于转移 BC B ‘0100’,address
BE address 等于转移 BC B ‘1000’,address
BNH address 不大于转移 BC B ‘1101’,address
BNL address 不小于转移 BC B ‘1011’,address
BNE address 不等于转移 BC B ‘0111’,address
以下指令用于算术指令之后
BO address 溢出转移 BC B ‘0001’,address
BP address 为正转移 BC B ‘0010’,address
BM address 为负转移 BC B ‘0100’,address
BZ address 为零转移 BC B ‘1000’,address
BNO address 不溢出转移 BC B ‘1110’,address
BNP address 不为正转移 BC B ‘1101’,address
BNM address 不为负转移 BC B ‘1011’,address
BNZ address 不为零转移 BC B ‘0111’,address
以下指令用于 TM 指令之后
BO address Branch If Ones BC B ‘0001’,address
BM address Branch If Mixed BC B ‘0100’,address
BZ address Branch If Zeros BC B ‘1000’,address
BNO address Branch If Not All Ones BC B ‘1110’,address
BNM address Branch If Not All Mixed BC B ‘1011’,address
BNZ address Branch If Not All Zeros BC B ‘0111’,address

- 224 -
附录B

IBM EBCDIC 编码表


(Extended Binary Coded Decimal Interchange Code)

第一个十六进制数字
0 1 2 3 4 5 6 7 8 9 A B C D E F
0 NUL DLE DS SP & ― { } \ 0
1 SOH DC1 SOS / a j ~ A J 1
2 STX DC2 FS SYN b k s B K S 2
3 ETX TM c l t C L T 3
第 4 PF RES BYP PN d m u D M U 4
二 5 HT NL LF RS e n v E N V 5
个 6 LC BS ETE UC f o w F O W 6
DEL IL ESC EOT
十 7 g p x G P X 7
8 CAN h q y H Q Y 8

9 EM i r z I R Z 9

A SMM CC SM ! : 1
制 VT CUI CU2 CU3
B . $ , #
数 FF IFS DC4 < * % @
C
字 CR IGS ENQ NAK ( ) - ‘
D
E SO IRS ACK + ; > =
F SI IUS BEL SUB | ﹁ ? “ EO

控制字符的含义:
ACK 接受 DC2 设备控制 2 FF 格式输入
BEL 铃 DC4 设备控制 4 FS 分隔符
BS 退格 DEL 删除 HT 横表
BYP 封锁 DLE 数据杆锁 IFS 换文分隔符
CAN 抹消 DS 选区数字 IGS 换组分隔符
CC 指示器控制 EM 介质结束 IRS 换记录分隔符
CR 回车 ENQ 查询 IUS 换单位分隔符
CU1 用户用 1 EOT 传送结束 LC 小写字母
CU2 用户用 2 ESC 锁杆 LF 换行
CU3 用户用 3 ETB 字组传送结束 NAK 负向接受
DC1 设备控制 1 ETX 文本结束 NL 新行
NUL 空 SMM 手动信息开始 SUB 替代
PF 停止穿孔 SO 移出 SYN 同步无效
PN 开始穿孔 SOH 标题开始 SI 移进
RES 复位 SOS 关键字开始 TM 磁带标记
RS 停止读 SP 空格 UC 大写字母
SM 建立方式 STX 本文开始 VT 垂线

- 225 -
附录C

常用指令表

名称 助记符 操作码 格式 操作数


Add AR 1A RR R1,R2
Add A 5A RX R1,D2(X2,B2)
Add Decimal AP FA SS D1(L1,B1),D2(L2,B2)
Add Halfword AH 4A RX R1,D2(X2,B2)
Add Logical ALR 1E RR R1,R2
Add Logical AL 5E RX R1,D2(X2,B2)
AND NR 14 RR R1,R2
AND N 54 RX R1,D2(X2,B2)
AND NI 94 SI D1(B2),I2
AND NC D4 SS D1(L1,B1),D2(L2,B2)
Branch and Link BALR 05 RR R1,R2
Branch and Link BAL 45 RX R1,D2(X2,B2)
Branch on Condition BCR 07 RR R1,R2
Branch on Condition BC 47 RX R1,D2(X2,B2)
Branch on Count BCTR 06 RR R1,R2
Branch on Count BCT 46 RX R1,D2(X2,B2)
Branch on Index High BXH 86 RS R1,R3,D2(B2)
Branch on Index Low or Equal BXLE 87 RS R1,R3,D2(B2)
Compare CR 19 RR R1,R2
Compare C 59 RX R1,D2(X2,B2)
Compare Decimal CP F9 SS D1(L1,B1),D2(L2,B2)
Compare Halfword CH 49 RX R1,D2(X2,B2)
Compare Logical CLR 15 RR R1,R2
Compare Logical CL 55 RX R1,D2(X2,B2)
Compare Logical CLC D5 SS D1(L1,B1),D2(L2,B2)
Compare Logical CLI 95 SI D1(B1),I2
Compare Logical Characters CLM BD RS R1,R3,D2(B2)
under Mask
Compare Logical Long CLCL 0F RR R1,R2
Convert to Binary CVB 4F RX R1,D2(X2,B2)
Convert to Decimal CVD 4E RX R1,D2(X2,B2)
Divide DR 1D RR R1,R2
Divide D 5D RX R1,D2(X2,B2)
Divide Decimal CP FD SS D1(L1,B1),D2(L2,B2)
Edit ED DE SS D1(L1,B1),D2(L2,B2)
Edit and Mask EDMK DF SS D1(L1,B1),D2(L2,B2)
Exclusive OR XR 17 RR R1,R2
Exclusive OR X 57 RX R1,D2(X2,B2)
Exclusive OR XI 97 SI D1(B1),I2
Exclusive OR XC D7 SS D1(L1,B1),D2(L2,B2)
Execute EX 44 RX R1,D2(X2,B2)
Insert Character IC 43 RX R1,D2(X2,B2)
Insert Character under Mask ICM BF RS R1,R3,D2(B2)
Load LR 18 RR R1,R2
Load L 58 RX R1,D2(X2,B2)
Load Address LA 41 RX R1,D2(X2,B2)
Load and Test LTR 12 RR R1,R2
Load Complement LCR 13 RR R1,R2

- 226 -
Load Halfword LH 48 RX R1,D2(X2,B2)
Load Multiple LM 98 RS R1,R3,D2(B2)
Load Negative LNR 11 RR R1,R2
Load Positive LPR 10 RR R1,R2
Load PSW LPSW 82 S D2(B2)
Move MVI 92 SI D1(B1),I2
Move MVC D2 SS D1(L1,B1),D2(L2,B2)
Move Long MVCL 0E RR R1,R2
Move Numerics MVN D1 SS D1(L1,B1),D2(L2,B2)
Move with Offset MVO F1 SS D1(L1,B1),D2(L2,B2)
Move Zones MVZ D3 SS D1(L1,B1),D2(L2,B2)
Multiply MR 1C RR R1,R2
Multiply M 5C RX R1,D2(X2,B2)
Multiply Decimal MP FC SS D1(L1,B1),D2(L2,B2)
Multiply Halfword MH 4C RX R1,D2(X2,B2)
OR OR 16 RR R1,R2
OR O 56 RX R1,D2(X2,B2)
OR OI 96 SI D1(B1),I2
OR OC D6 SS D1(L1,B1),D2(L2,B2)
Pack PACK F2 SS D1(L1,B1),D2(L2,B2)
Set Program Mask SPM 04 RR R1,R2
Shift and Round Decimal SRP F0 SS D1(L1,B1),D2(L2,B2)
Shift Left Double SLDA 8F RS R1,R3,D2(B2)
Shift Left Double Logical SLDL 8D RS R1,R3,D2(B2)
Shift Left Single SLA 8B RS R1,R3,D2(B2)
Shift Left Single Logical SLL 89 RS R1,R3,D2(B2)
Shift Right Double SRDA 8E RS R1,R3,D2(B2)
Shift Right Double Logical SRDL 8C RS R1,R3,D2(B2)
Shift Right Single SRA 8A RS R1,R3,D2(B2)
Shift Right Single Logical SRL 88 RS R1,R3,D2(B2)
Store ST 50 RX R1,D2(X2,B2)
Store Character STC 42 RX R1,D2(X2,B2)
Store Characters under Mask STCM BE RS R1,R3,D2(B2)
Store Halfword STH 40 RX R1,D2(X2,B2)
Store Multiple STM 90 RS R1,R3,D2(B2)
Subtract SR 1B RR R1,R2
Subtract S 5B RX R1,D2(X2,B2)
Subtract Decimal SP FB SS D1(L1,B1),D2(L2,B2)
Subtract Halfword SH 4B RX R1,D2(X2,B2)
Subtract Logical SLR 1F RR R1,R2
Subtract Logical SL 5F RX R1,D2(X2,B2)
Supervisor Call SVC 0A RR R1,R2
Test under Mask TM 91 SI D1(B1),I2
Translate TR DC SS D1(L1,B1),D2(L2,B2)
Translate and Test TRT DD SS D1(L1,B1),D2(L2,B2)
Unpack UNPK F3 SS D1(L1,B1),D2(L2,B2)
Zero and Add Decimal ZAP F8 SS D1(L1,B1),D2(L2,B2)

- 227 -
附录D

参考文献

[1] Assembler Language with ASSIST and ASSIST/I, Ross A. Overbeek & W.E. Singletary,
Prentice-Hall, 1991
[2] MVS 操作系统用户指南,朱卫东、任素芹、林幼萍、黄红著,中国铁道出版社,1996
[3] 汇编语言程序设计(IBM370 系统),胡久清、陈世鸿、何炎祥编,1985
[4] 新时代大型计算机--IBM S390 系列,徐拾义、肖慕岳、陈晋隆,浙江大学出版社,1999
[5] IBM High Level Assembler for MVS & VM & VSE Release 2 Presentation Guide, IBM
Redbook, Document Number SG24-3910-01
[6] HLASM for MVS & VM V1R2.0 Gen. Information, IBM Redbook, Document Number
GC26-4943-01
[7] HLASM MVS & VM Language Reference V1R2, IBM Redbook, Document Number
SC26-4940-01
[8] HLASM MVS & VM Programmer's Guide V1 R2, IBM Redbook, Document Number
SC26-4941-01

- 228 -

You might also like