组合语言之艺术
第三节 模块观念

  当宇宙中还没有人类文明时,是一个整体,循着一定的规律,无休无止地运行。人
类的出现,因为意识到自我与非我的分别,就产生了是、非,有、无,善、恶,好、坏
的认知。世事的复杂性,即源自于这种分辨心,而忽略了本体的完整及规律性。
    人类对知识的追求,是利用概念作为工具,有系统地对宇宙各种现象分门别类的分
析。而分析的结果,可以视为各种模块,人则借着各式模块,来认知宇宙这个大环境。
    因此模块并不局限于具体的、客观的事物,任何可以经由分析、归纳而认知者,均
具有模块结构。而且每每在一些模块的结构上,又有另一层次的模块展开。
    计算机软件的模块,则是一些功能或性质独立的「目标档」(OBJ Files )。此类
目标档与界面模块相互联接后,即可执行。为了追求工作效率,将此类模块有系统的予
以分类整理,公开上市,不仅可以避免程序的重复制作,且可便利程序员的组合应用。
    「集装货柜」增进了航运的效率,其原因就在于将货物「模块」化,有的以运送目
的地、有的以货物性质而定。此外,如「预铸房屋」,甚至于流水线式的生产等,都具
有相同的观念。这和程序制作时,为了效率所作的模块分割,有异曲同工之妙。
    软件模块化的要求由来已久,此与工业生产的经验有关,但是迄今很少有具体的成
功先例。因为软件的变化无穷,很难建立一种「工业标准」,既没有标准,就难以统一
规划。
    因为标准是人制订的,在初,不论如何深思熟虑,不周之处在所难免。但只要有了
先例,而且能提供大家参考、应用,集合众人之智能,总会有完善的一天。
    问题在于,当今有经验的程序员们太忙了,有的忙于自我创造,有的忙于模仿抄袭,
有的则忙于享受、玩乐。学校里所教的,全是与现实脱节的理论,而一出校门,由于软
件程序员供不应求,炙手可热,立刻就投入了市场的洪流,推波逐澜,还有谁管什么模
块!
一、模块定义
  1,空间小、结构完整,能独立调用的程序。
 模块不能太大,否则无法灵活应用,程序的结构又必须独立且完整,以便于分解、组合,
供二次开发的程序自由调用。
 有些模块需要与公用模块配合使用,由于公用模块为全部模块设计的基础,故仍可视为
独立调用。
  2,效率高、弹性大,便于其它程序调用者。
 二次开发的程序系建立在模块结构上,如果模块的效率不佳,必然会影响其操作。同时,
各种应用程序都有特殊的需求和条件,而模块则要考虑通用性,否则很难符合各种条件。
所以,在设计模块时,要考虑应用的弹性,使之既能适应各种需求,又能达成特殊的任
务。
  3,功能明确,其变化以参数设定实现。
 功能不明确,将会令应用者困惑,也就达不到预期的目的。功能明确与否,除了程序本
身外,手册及说明也占了极大的比重。
 所谓功能明确,并非指明确的单一功能,而是指功能的分类。在分类时,需要有一种容
易分辨的方式,而最简单的,便是设置参数,凭参数作分支的条件。
  4,程序之间必须利用寄存器传送数据者。
 基于程序、模块之间相互独立,各自应用的缓冲器无法统筹运用。故在执行时,必须利
用寄存器以传数据或参数,完成彼此的沟通。
二、模块种类
    将模块分类,为的是便于说明,以下的分类法,并非一成不变。原则上,模块可分
为:
  1,功能模块:以所执行的功能加以分割者。
 此类模块要考虑应用上的方便及功能的完整。然而,所谓功能的完整,只限于独立、单
一的目标,绝不可将多个不同的功能,设计在一个难以分割的模块中。
  2,公共模块:没有明确的功能,但具有共享的效益,或其它某种特殊的目的,也可以
设计成为模块。
 这种模块经常附属于其它模块中,设计时应注意其调用的灵活性,并应专设一目录,详
细记载其用途,以便随时查寻。
  3,界面模块:介于两「面」之间,以解决两者问题之模块。一般在程序中,凡属数据
与数据间、程序与程序间、硬件与硬件间者,皆为界面关系,处理这种关系的模块即为
界面模块。
  4,应用模块:应用者为人,工作者为程序,故提供给应用者操作的模块,概称应用模
块。
 此种模块在实质上,仍属一种界面,但因为其实用价值关系到一个程序的成败,必须独
立考虑。
 这种模块变化最大,随时有必要根据使用者的需求修改增减,故设计时要特别留意。
三、模块分割的基本原理
    中国文化之博大精深,可以由古人的思想略窥一二,许多人自以为学了一点西方科
学技术的皮毛,就像夜郎一样了不得了。其实,除了知识在不断累积外,从古至今,人
的智能并没有丝毫增长。
    模块是一种极有价值的观念,任何事物的形体、结构、步骤,甚至于概念、认知等,
都可在以某个目标为前提之下,分析成为若干模块。有了模块,范围就会缩小、问题也
就变得单纯,比较容易掌握。如果能有效地利用模块,以之作为解决问题的手段,将是
无往而不利。
    庄子早见于此,曾经以寓言的方式,在其内篇《养生主》中,就曾对模块的分割,
作了透彻的剖析介绍。时到今日,科学昌明之际,读来仍有「振聋起聩」,一新耳目之
慨。
    为此,特将原文抄录于下,再作浅释。
 养生主第三
    吾生也有涯,而知也无涯。以有涯随无涯,殆已。已而为知者,殆而已矣。为善?近
名,为恶?近刑。缘督以为经。可以保身,可以全生,可以养亲,可以尽年。
    庖丁为文惠君解牛,手之所触,肩之所倚,足之所履,膝之所踦。砉然向然,奏刀
騞然,莫不中音,合于桑林之舞,乃中经首之会。
    文惠君曰:『嘻,善哉,技盖至此乎?』
    庖丁释刀对曰:『臣之所好者道也,进乎技矣。始臣之解牛之时,所见?非牛者。三
年之后,未尝见全牛也。方今之时,臣以神遇,而不以目视。官知止而神欲行,依乎天
理。批大却,导大窾,因其固然。技经肯綮之未尝,而况大軱乎?良庖岁更刀,割也;
族庖月更刀,折也。今臣之刀十九年矣,所解数千牛矣,而刀刃若新发于硎。彼节者有
闲,而刀刃者?厚;以?厚入有闲,恢恢乎其游刃必有余地矣,是以十九年而刀刃若新发
于硎。虽然,每至于族,吾见其难为,怵然为戒。视为止,行为迟。动刀甚微,謋然已
解,如土委地。提刀而立,为之四顾,为之踌躇满志,善刀而藏之。』
    文惠君曰:『善哉!吾闻庖丁之言,得养生焉。』
  1,前文释义
 人的生命有限,而知识无尽,以有限之生命追求无尽的知识,是自陷于绝地。既已自陷,
还妄想借助于知识,以求自救,当然更是匪夷所思了。
 其实,只要了解事物的道理,以及各种问题的症结。不要执着于表象,不要迷惑于现状,
顺理而行。如是,不仅可以安身立命,且能功成名就。
 梁惠王有一厨师,宰牛时,姿态潇洒,动作俐落,颇有艺术家的风采。梁惠王见了,赞
不绝口:
 『真了不起!你的刀法神奇到这个地步!』
 厨师听了,忙把刀放下,向梁惠王禀告说:
 『臣子我一向重视观念,并不在意小技巧。最初,臣宰牛时,只看到牛的全身。又过了
三年,每宰牛时,所看到的只是牛的结构。现在,臣已能心领神会,按照自然的原则,
不论是剖肉、卸骨,刀尖只要顺着关键的间隙轻轻一挑,其组织立即迎刃而解。
 好的厨师每年换刀,这是因为切割过多,难免有所损耗;技术较差的,经常用力砍,刀
锋易折,每月都需要换一次。而臣所用之刀,已经有十九年了,宰牛不下千头,而刀刃
锋利如昔。
 其实,道理很简单,牛体是由很多不同的组织组成,其间必然有衔接的空间。因为刀锋
很薄,在组织间隙中移动,轻轻松松,甚至还有多余的地方。
 当然,也有碰到棘手时。臣就会全神贯注,先找到问题所在,小心谨慎地处理。一旦刀
锋稍动,剎时之间,组织分离,大功告成。
 那种成就之后欣慰的感受,简直难以比拟。最后,为了下次的工作,把刀擦拭干净,好
好保护收存。』
 梁惠王听了,道:『好极了,听你一席话,领悟到不少人生的大道理。』
  2,主旨精要
 人生是有限的,为了有效的利用精力,以解决一切问题,应该学习掌握事物的原理。原
理之一,即为事物皆具有模块的结构,模块是事物组成之基础。有能力的人,一定善于
分析事理,了解事物的结构基础,得以从容不迫,在面对问题时,找到有问题的模块,
在其症结上下刀,问题便会迎刃而解。
四、模块分割
    既然称为模块,本不存在分割与否的问题。如果一个程序员一开始就具备模块的观
念,彻底了解其性质,当然知道如何设计,自然就没有分割的必要。即令如此,在程序
的制作过程中,经常是信马游缰,想到哪,写到哪。所以,养成模块分割的观念,对实
际工作上,自有其必然的功效。
    模块分割的原因不定,大约可分下列数种:
  1,为了制作的方便,把程序分成模块,易于维护。
  2,为了工作效率,或需要速度、或为节省空间的程序,分别用不同的技巧制作。
  3,因程序员的工作能力,或工作条件而进行分割。
  4,因程序的功能分类,分开制作,以便于灵活应用。
    不论原因为何,分割模块前,一定要先确定目标,全面认知程序的格局,再加以整
理、归类,才能根据类别来分割。
    分类确定后,再以分类来命名,以便于记忆及应用。这时格式的统一定义便是关键
所在。因为模块一多,相互间的调用极为频繁,如果格式不能统一,程序间常常需要因
应调整,反而增加了使用上的难度,得不偿失。
    其次,各模块必须建立一个总表,而且要经常维护,务必与实际上所使用者一一对
应,切勿偷懒。每次调用时,还需修正记录,如使用次数,调用的程序等。
    如果程序制作的规模大,参与人数多,最理想是由专人负责模块管理。从事这种工
作的人,必须头脑清楚,反应敏锐,而且要能任劳任怨,勤于更新。
    在做模块分割前,应先备妥 .REF 及 .LST 档,设有一程序 ABCD.ASM ,其做法为:
    C:>M ABCD,,,;
    M 为汇编程序,即 MASM.EXE 之简化名,请参见第二章第二节。其后之",,,;+-
*/表示需要 .OBJ,.LST和 .CRF三个档,而且不必显示在屏幕上。
    汇编完成后,再用 CREF.EXE 生成 ABCD.REF 如下:
    C:>CREF ABCD;
    这时,将 ABCD.LST 及 ABCD.REF 印出,相互对照,先将程序精简、整理,再行分
割。
  1,在 ABCD.REF 档中,每个「标题」都来自 ABCD.LST 档,标题之后,有若干组数字,
其中带#者,表示标题出处,余者为调用之处。若仅有来处,而无调用处,则此标题可
删除。再如标题前之指令为 JMP 或 RET ,则此标题所代表的程序毫无用处,亦应删除
之。
  2,如 ABCD.LST 档中有 NOP指令,除非是有意安排者,否则亦应删除。如在 JMP XXX
X 之后,可将之改为 JMP SHORT  XXXX 。
 若 XXXX 之前无连接的程序,亦可将之合并。此外,凡指令 CALL 所调用的子程序,如
仅调用一次,最好将此段子程序合并在程序内,或附属于其后,以便于分辨。
  3,程序精简及合并后,再行检查各段程序,凡功能独立者,应先分割。分割后,再检
查其中是否调用其它子程序,如有,应先记录下来,此段程序即可视为「功能模块」。
  4,凡前述功能模块中调用的子程序,如仅供该模块调用,则可附在该模块后,否则应
置于「共享模块」中。
  5,但凡程序之「主流程」,大多属「应用模块」。此类应用模块极难分割,也无分割
的必要。
  6,分割完毕后,应详细注记,以便备用。
五、模块特性
    所谓模块特性,是指各个模块在设计或分割之初所考虑的因素。特性包括了功能,
效率的特别要求以及使用时应行注意的重点等有关模块的细节。
    不论模块设计的目标为何,既然有了模块,就应该高度发挥其应有的效率。各个模
块之分割,皆有其必然因素,不论是为了功能,或是为了制作方便,目的都在追求效率。
而每个模块皆有其特性,要达到效率要求,应先了解模块的特性。
    下面以前节所举的中文系统为例,将其中各个模块的特性一一详细分析如下:
    【模块一】:中文处理的系统模块-功能模块
     1,键盘中断:因为人输入的速度远逊于计算机处理速度,所以,本中断程序应该
以节省空间为主。此外,键盘的应用,以灵活达变为重点,宜多采用「建表」法,便于
修改、变更。
     2,显示中断:显示速度非常重要,本模块应牺牲空间以争取时效。此外,显示的
弹性要大,凡字符的大小、位置、属性等,都应该一次考虑清楚。
     目前显示器的规格很多,彼此互不兼容,一般多在程序加载前,先作安排。在本模
块中,尤其应该注意不同的显示程序,如何兼顾速度的运行。
     3,打印中断:打印也面临不同的机种界面问题,但因不涉及速度,其技巧完全属
于设定及加载方面。
     4,通讯中断:通讯所需考虑的,是如何保证在数据传输过程中的安全、正确及保
密。
    【模块二】:中文内码模块-辅助模块
   因为仓颉码可以组出既有的,以及排列组合上可能产生的中文字,照理应该不受内码
的限制。但是仓颉输入码之长度不定,为了计算机的处理效率,我们采用了四字符内码,
每一字符的最高位设置为1 ,以与英文字符有别。
   内码的转换也应考虑速度,而且要能双向转换,程序制作不难,但处理的技巧也不低。
    【模块三】:中文字形结构模块-界面模块
   贮存中文基本字形结构,根据输入的仓颉码,得到文字结构的基本讯息,以之组成字
形。
   中文字数极多,字形结构的规划是成败的关键,只要每个字形多加几个字符,空间就
会恶性膨胀。
   本模块采用多层结构的技巧,其中子模块甚多,空间及速度兼顾,才能在极小的空间
中,完成大量字形输输出,而且变化灵活,完全拜模块分割之助。
    【模块四】:字形分析模块-辅助模块
   举凡字形大小、种类、笔形的变化及位置等,皆在本模块中完成。
   字形分析以变化多为目的,故本模块主要功能在处理变量,应该妥善安排各缓冲器,
以达到效率要求。
    【模块五】:英文字形模块-功能模块
   英文字形与中文有异,所以另成一模块。其字数较中文为少,但却不具备任意组合的
功能。
    【模块六】:绘图模块-功能模块
   这段程序是字形产生器的核心,当得到模块四、五的数据以后,要以高速将字形绘出。
   绘图程序的关键在计算,计算虽然是计算机的原始功能,如果不另外加上「辅助计算
器」,计算机的运算效率就大为降低。
   一般说来,仅在屏幕上绘图,或做字形绘制时,其范围有限,且可以预知,故可采用
「对照表」或快速运算法,换取显示效率。在打印输出时,则可采高精度运算方式。
    【模块七】:各种界面-应用模块
   界面即为介于两个模块,或两个独立的系统间的机构,在大型模块工程中,界面之良
窳,是使用效率的关键所在。
   界面是各个模块得以顺利配合的重要程序,只要了解界面的结构,便可以轻易地与原
程序沟通。
六、模块调用
    模块的先决条件是要能灵活调用,否则就失去了其设计的意义。而且模块的目的之
一,是为了提供给其它程序员,作二次开发用。所以调用的方法,至关重要,必须面面
俱到。
    调用的方法很多,为了兼顾多方面的效应,我主张利用系统程序的中断处理。这样
做的好处是,系统设计者不必考虑模块的设置,应用的程序员也无须担心入口的位置,
直接以参数调用即可。更有利的一点,则是可以在空间不足时,利用覆盖的技术,灵活
调用贮存在磁盘中的其它模块。
    「中断」是系统所提供的公用界面,其唯一的缺点是执行效率较差,但应用的程序
员可以将中断所提供的地址,移到自己的缓冲区内,代之以长距离的调用,即可改进之。
    以下且以中文字形产生器应用界面为例,说明模块调用方法。本字形产生器提供见
诸字典、文献之有效汉字六万余,仓颉输入法所可能组合产生的「新字码」约近六百万
字,在本字形产生器中皆有相应之字形。
    除了字数外,字级由1*1 至 128*128,无级次变化。字体在目前仅提供明体、黑体、
圆体及长、扁宋等,其横直粗细比值由1至8点,上限为直粗的十二分之一。字形变化
有空心、斜体及十种填花体、横向粗细变化体等。
    至于繁体、简体字形或 ASCII字符,也可由使用者自选。
    ┌────┬──────────────────┬─────────┐
    │功  能│  参  数  说  明      │  备  注     │
    ├────┼──────────────────┼─────────┤
    │BX=0  │                  │         │
    │取字形  │DL=n, n 为字形点阵左端字符的起点位置│n=0-7,使用者自定  │
    │    │  *  *   *           │         │
    │    │1,中文状态: 置五个仓颉序码     │序码= 仓颉码 A至Y │
    │    │  DS:[SI],[SI+1],[SI+2],[SI+3],   │   即序位 1至25 │
    │    │    [SI+4]            │[SI-1]=0     │
    │    │  例:[01,00,00,00,00] 为'日'   │00表示无输入码  │
    │    │    [14,07,08,01,06] 为'鹳'   │         │
    │    │  *  *   *           │         │
    │    │2,英文状态:             │         │
    │    │  [SI]=ASCII           │         │
    │    │   20H= 英文为中文之半       │半角       │
    │    │   7FH= 英文同中文大小       │全角       │
    │    │   2EH= 连续虚点          │用做删节号    │
    │    │  例:[SI]=41H 为'A'        │         │
    │    │  *  *   *           │         │
    │    │3,特殊状态:             │         │
    │    │  [SI]=ASCII           │ASCII专用     │
    │    │  [SI-1]=指令           │         │
    │    │   11H= 直向排列 (转90度)     │         │
    │    │   40H= 外加圆圈, 如1123      │最多为 3个字符  │
    │    │   C0H= 外加圆圈且反白,2AB     │         │
    ├────┼──────────────────┼─────────┤
    │BX=1  │无                 │         │
    │  繁体字│                  │         │
    ├────┼──────────────────┼─────────┤
    │BX=2  │无                 │         │
    │简体字  │                  │         │
    └────┴──────────────────┴─────────┘
    ┌────┬──────────────────┬─────────┐
    │功  能│  参   数   说   明     │   备   注    │
    ├────┼──────────────────┼─────────┤
    │BX=3  │                  │         │
    │定义  │CL=0 单线体 直粗一点        │供横向19点以下用  │
    │    │CL=1 细明 直粗两点         │         │
    │    │CL=2 轻明 直粗三点         │         │
    │    │CL=3 中明 直粗四点         │         │
    │    │CL=4 粗明 直粗六点         │         │
    │    │CL=5 重明 直粗八点         │         │
    │    │  *  *   *           │         │
    │    │CL=129 细黑 横直粗二点       │         │
    │    │CL=130 轻黑 横直粗三点       │         │
    │    │CL=131 中黑 横直粗四点       │         │
    │    │CL=132 粗黑 横直粗六点       │         │
    │    │CL=133 重黑 横直粗八点       │         │
    │    │  *  *   *           │         │
    │    │CL=149 中圆 横直粗四点       │限用于48点以上  │
    │    │CL=150 粗圆 横直粗六点       │同上       │
    │    │CL=151 重圆 横直粗八点       │同上       │
    │    │  *  *   *           │         │
    │    │DH=△ X               │即X2-X1      │
    │    │DL=△ Y               │即Y2-Y1      │
    ├────┼──────────────────┼─────────┤
    │BX=4  │                  │         │
    │空心体  │DL=0 取消空心体           │         │
    │    │  =1 设定空心体           │         │
    ├────┼──────────────────┼─────────┤
    │BX=5  │                  │         │
    │斜体  │DL=0 取消斜体            │         │
    │    │  =1 设定斜体            │横向右斜纵向的1/8 │
    └────┴──────────────────┴─────────┘
    ┌────┬──────────────────┬─────────┐
    │功  能│  参   数   说   明     │   备   注    │
    ├────┼──────────────────┼─────────┤
    │BX=6  │                  │         │
    │填花体  │DL=0 取消填花体           │         │
    │    │  =1 设定填花体,其中        │         │
    │    │  DH=0 全黑,与空心体共享则增大一点 │         │
    │    │  =1 粗点形            │         │
    │    │  =2 细网形            │         │
    │    │  =3 疏斜线向右          │         │
    │    │  =4 密斜线向左          │         │
    │    │  =5 残碑形            │         │
    │    │  =6 水泡形            │         │
    │    │  =7 直柱形            │         │
    │    │  =8 梅花形            │         │
    │    │  =9 龙纹形            │         │
    ├────┼──────────────────┼─────────┤
    │BX=7  │                  │         │
    │加宽形  │每次直加粗一点,至8 点为止     │         │
    ├────┼──────────────────┼─────────┤
    │BX=8  │                  │         │
    │减细形  │每次直减细一点,至1 点为止     │         │
    ├────┼──────────────────┼─────────┤
    │BX=9  │                  │         │
    │加厚形  │每次横加厚一点,但不超过8 点    │         │
    ├────┼──────────────────┼─────────┤
    │BX=10   │                  │         │
    │减薄形  │每次横减一点,最细为1        │         │
    └────┴──────────────────┴─────────┘
    第二种方法,则是提供模块的目标码 (.OBJ ),由应用程序员自行联接成执行档。
这种做法,每个程序相互独立,兼容性不高,通用性也不强,如果同时想应用多个程序,
则需要极大的系统空间。
    小型且专用性极强的应用程序,适用这种方法。但模块一旦与应用程序联接后,就
很难再灵活应用。

上一页    下一页