首先发发牢骚吧(并未企图破坏论坛内人员团结,也非煽动);2010年初开始自学汇编语言(16位),如果单就书本上的代码练习练习倒是没有什么问题,但是想学好编程不可能只是对照着书本来吧。所以就在网络上搜了一些练习的代码,而后找到<25个经典汇编程序>应该算是目前比较全的汇编练习代码集吧。
  当然迫不及待的打开,迫不及待的去试验试验自己的那点水平能否看懂里面的代码。结果呢,真的很令人失望,能看懂的还真少。于是就下决心去专研,当然肯遇到困难了但是经过一段时间后当我对汇编熟悉有一定了解,我发现了几个让我痛心疾首的问题:
  1.写代码的人相当的不负责任,可能连调试阶段都没有经过只要程序能够运行不管结果对与错就认为完成了工作。他们的目的是看见有 .exe 就认为对了,也不管能不能达到任务的需求。
  2.百度中的代码基本上是一大抄,人家错哪他们也错哪。好像对自己的学习处于应付的状态,提问的人想不明白、回答的人说不明白。一个个就在应付,我很想知道这是应付谁呢?有问题上百度这应该是现代人一个比较普遍的现象,可是现在百度能解决问题的又有多少?
  3.基本上没有注释,没有任何说明。对于初学者来说,去看别人的代码本来就是件不容易的事情;更何况去分析和理解最终掌握。写代码的人为什么不想想,你的代码思路清晰能帮助多少个所谓的"菜鸟"们成长。为什么国内的编程水平这么差,很大一部分原因就在于人们的态度。对待学术问题丝毫不严谨,对待自己的学业是应付。
  4.毫无格式可言。在<25个经典汇编程序>代码集中大部分代码都是"混乱分布",写代码的人大多数连空格都不舍得放那更别说空行了。代码全都挤在一起,就好像是一堆杂乱的毛线无从捋顺。
  个人建议,请写代码的前辈们真的要负责一点。中国人的责任心是在国际上都收抨击的,我们不应该引以为豪。请想学习的朋友们对自己认真负责一点,如果每个人都用心去对那些不负责任的前辈们的代码仔细阅读就会发现很多问题。当你发现了问题你就应该告诉我们的学弟学妹们,不让他们在为这"天书"一般的代码去发愁。我们的代码都应该有足够的注释,无论多简单。我们的代码要足够清晰即使是不会编程的人也能看出个所以然。试想下,如果前辈们可以做到这样那可以缩短后来人都少在菜鸟阶段的时间;如果前辈们都认真对待自己的程序,网络上又怎么可能出现那么多人云亦云的现象。请让你的代码清晰一点点,这花不了你多少时间。你的代码你日后能很快的温习过来,也可以造福后来人他们阅读你的代码。。。。
  确实罗嗦了一点,但是这却积压在我心中很久的郁闷。原本打算把<25个经典汇编程序>代码集中所有代码都改最清晰的方式在重新发出来,可是越改我就越难受越难受我就越想说;最后我就一吐心中不快,全部的说出来。
  希望我修改的代码能对和我一样在初学阶段的朋友们一点点提示,缩短"菜鸟"阶段的时间。

  以下是判断闰年的汇编代码(16位)

;程序名称:判断闰年
;功    能:判断闰年

assume     cs:code, ds:data, ss:stack_

data       segment
       Print_mess     db 0dh, 0ah,   'Please input a year: $'
       Resault_1      db 0dh, 0ah,   'This is a leap year! $'
       Resault_2      db 0dh, 0ah,   'This is not a leap year! $' 
       
       Write          dw   0
       ;用于保存转换后的输入数据的值(年份),年份的输入时ASCII形式的,必须将其转换为16进制形式的
       ;才能予以正常处理
       
       Buf            db   5
                      db   ?
                      db   5 dup (?)
       ;整个BUF功能和21h号中断功能0ah是对应的 
data       ends

stack_     segment   stack
       db  200 dup (0)
stack_     ends

code       segment
start:
       mov       ax,     data
       mov       ds,     ax
       ;
       lea       dx,     Print_mess
       mov       ah,     9
       int       21h
       ;作用是将提示信息显示出来 
       ;
       lea       dx,     Buf
       mov       ah,     0ah
       int       21h
       ;作用是等待用户输入的数据以便可进行判断 类似C语言的scanf
       ;
       mov       cl,     Buf + 1
       xor       ch,     ch
       ;Buf+1是实际输入数据的个数的地址,然后将该值赋予 CL 
       ;必须清 ch 因为在程序开始时cx中记录了该程序使用的字节数而ch中很可能有数据
       ;该数据可能会影响后面的操作,或者调试时候的判断  
       ;强调:在原著中没有 xor       ch,     ch  此句,如果调试是绝对看不见正确结果的 

       call      Asc_to_Hex
       call      Judge_
       ;Judge 判断 
       jc        Print_Resault
       
       ;不是闰年 
       lea       dx,     Resault_2
       mov       ah,     9
       int       21h
       jmp       EXIT

       ;是闰年       
Print_Resault:
       lea       dx,     Resault_1
       mov       ah,     9
       int       21h
       
EXIT:
       mov       ah,     4ch
       int       21h

       
;***********************************************************************************************       
;子程序名:Asc_to_Hex
;功    能:将以ASCII形式输入的数据转换成为16进制表示的数据
;入口参数:CX = Buf+1 实际输入数据的个数 
;出口参数:

Asc_to_Hex       PROC     NEAR
       push      cx
       dec       cx
       lea       si,     Buf + 2
       
Point_tail:
       inc       si
       loop      Point_tail
       pop       cx
       
       mov       dh,     30h
       mov       dl,     10
       mov       bx,     1
       mov       ax,     0
       ;
To_Asc:
       xor       ah,     ah
       ;sub       byte ptr [si],     dh 
       ;mov       al,     byte ptr [si]   警告: 破坏了原始数据 原著中存在 
       mov       al,     byte ptr [si]
       sub       al,     dh
       ; [si] 原本就指向一个字节数据 添加 byte ptr 作用是避免出错 起强调作用 也是个好的习惯 
       
       push      dx
       mul       bx
       pop       dx
       ;由于在乘除法运算过程中 DX AX 会被作为同一个寄存器来处理,故 DX 中的数据会被改变
       ;若不加数据保护 push pop 则 DX 中的数据会被清零,自然也达不到程序处理的要求
       ;原著中没有 push dx 和 pop dx 所以原著始终达不到正确结果 
       
       add       Write,   ax
       
       push      ax
       mov       ax,      bx
       mul       dl
       mov       bx,      ax
       pop       ax
       
       dec       si
       loop      To_Asc
       
       ret
Asc_to_Hex       ENDP
;***********************************************************************************************


;***********************************************************************************************
;程序名称:Judge_
;功    能:判断是否是闰年
;入口参数:Write
;出口参数:判断结果 

Judge_           PROC     NEAR
       push      bx
       push      cx
       push      dx
       
       mov       ax,      Write
       mov       cx,      ax
       ;对年份数据备份 
       mov       dx,      0
       mov       bx,      4
       div       bx
       cmp       dx,      0
       ;检查余数,通过检查余数可以得知该数数否能被整除 
       jnz       Judge_1
       
       mov       ax,      cx
       mov       bx,      100
       mov       dx,      0
       div       bx
       cmp       dx,      0
       jnz       Judge_2
       
       mov       ax,      cx
       mov       bx,      400
       mov       dx,      0
       div       bx
       cmp       dx,      0
       jz        Judge_2

Judge_1:
       clc
       ; Clear Carry Flag 清进位标志 CF=0表示不是闰年 
       jmp       Judge_3
       
Judge_2:
       stc
       ; Set Carry Flag   置进位标志 CF=1表示是闰年
       
Judge_3:
       pop       dx
       pop       cx
       pop       bx       
       ret
Judge_           ENDP
;***********************************************************************************************

code             ENDS
                 END     start  

以下补充算法分析:

  • 标 题:再议汇编判断闰年(16位实模式) 补充
  • 作 者:hjzco
  • 时 间:2010-06-24 16:36:36

判断闰年的算法:(year%4==0)&&(year%100!=0)||year%400==0
一个年份若它能被4整除同时不能被100整除,或能被400整除的年份是闰年

;子程序名:Asc_to_Hex
;功    能:将以 ASCII形式输入的数据转换成为16进制表示的数据
;入口参数:CX = Buf+1
;出口参数:

Asc_to_Hex       PROC     NEAR
       push      cx
       dec       cx
       lea       si,     Buf + 2

;以上内容是将指针计数器 cx 减少一位,作为以后面算法的需要
       
Point_tail:
       inc       si
       loop      Point_tail

;此处就是执行将指针向后移动
;例:首先假设我们输入的是4位数字的年份(例如:2004) cx=4
;以上对CX必须自减1的原因是(以下模拟内存)
;   | ? | 2 | 0 | 0 | 4 | ? |
;         ^(lea si, Buf + 1 指针指向首地址)          如果我们不自减1 cx=4,而计算4次指针会指向未知数据
;如 | ? | 2 | 0 | 0 | 4 | ? |
;                         ^    <-移动 cx=4 指针此时指向的是未知的数据,必然导致输出的错误
;正确应该如下
;   | ? | 2 | 0 | 0 | 4 | ? |
;                     ^        <-cx=3

       pop       cx
       
       mov       dh,     30h
       mov       dl,     10
       mov       bx,     1
       mov       ax,     0
       ;
     
;以上属于赋初值,每个寄存器均有独到的作用
;mov dh, 30h 作用:因为键盘输入的内容属于ASCII码形式,可我们需要的数据是16进制形式,所以要进行转换
;将原ASCII数据 减去 30h 就可以得到对应的16进制数据的形式
;mov dl, 10  作用:作为一个固定的乘积,为以下算法分析铺垫
;mov bx, 1   作用:乘数(数据会改变)
;mov ax, 0   作用:存数用的寄存器(数据会改变)


To_Asc:
       mov       al,     byte ptr [si]
       sub       al,     dh

       push      dx
       mul       bx
       pop       dx
       
       add       Write,   ax
       
       push      ax
       mov       ax,      bx
       mul       dl
       mov       bx,      ax
       pop       ax
       
       dec       si
       loop      To_Asc
       
       ret
Asc_to_Hex       ENDP

;To_Asc的作用:是将每个输入的ASCII码数据提出并且将其变换成为16进制形式
;当把所有数据转换成为16进制形式后,并将其求和计算出对应的数据
;仍然以输入数据 2004 为例
;键盘输入 2004 的数据形式如下: 2(32h) 0(30h) 0(30h) 4(34h) 这些数据都是独立的不能进行我们所需的处理
;如果数据是ASCII码则不能进行闰年判断了,闰年的判断算法是一个年份若它能被4整除同时不能被100整除,或能被400整除的年份是闰年.
;我们需要得到的数据是 2004d(十进制) = 7d4h(十六进制) 而不是独立的ASCII码,所以要将输入的数据转换 这就是和高级语言的差别

;虽然将独立的ASCII码转换成16进制数据,但是依然不能对其进行处理,因为它们仍然是独立的所以要将其合并
;合并的算法: 个位*1 + 十位*10 + 百位*100 + 千位*1000 ... 以此类推
;为什么不从最高位开始,那是因为你不知道输入的到底是哪个年份有可能输入的是 500年 也有可能输入的是10000年
;以上为准备知识,现在对代码进行分析

;       mov       al,     byte ptr [si]
;       sub       al,     dh
;  这段代码的主要作用就是将数据从最后一位开始读取,并且从ASCII形式转换成为16进制形式,然后将其送入寄存器al中

;       push      dx
;       mul       bx
;       pop       dx
       ;由于在乘除法运算过程中 DX AX 会被作为同一个寄存器来处理,故 DX 中的数据会被改变
       ;若不加数据保护 push pop 则 DX 中的数据会被清零,自然也达不到程序处理的要求 
       
;       add       Write,   ax

        ;bx则是位后面的乘数(个位*1 + 十位*10...)
        ;Write 就是将一个个独立的数据相加求和 并且把最终的结果储存
        ;例: (34h-30h)+(30h-30h)+(30h-30h)+(32h-30h) = 2004d = 7d4h

;       push      ax
       ;push      dx  该句就可以添加保护
;       mov       ax,      bx
;       mul       dl
;       mov       bx,      ax       ;bx = 1 或 10 或 100 或 1000
;       pop       ax
;       bx就是乘数 但是最核心的地方在于它保证 bx 每次都是以10倍数增长 但又不破坏其他的数据,于是使用了中间变量dl

;提示:由于使用了寄存器DX,所以在定义数据大小时就限制了输入数据的长度
;输入过长的数据必然导致DX被更改,那样将影响数据的正确性
;该问题可以通过加寄存器保护(push)就可以升级输入数据的长度

  • 标 题:再议汇编判断闰年(16位实模式) 补充
  • 作 者:hjzco
  • 时 间:2010-06-24 16:57:21

以上就是判断闰年程序修改后的全部代码,希望对大家有所帮助。
 有一句话我想说给初学的朋友们听,把这段代码修改正确我是经过了很长的一段时间。原先以为修改对了,可是过段时间再调试的时候我依然发现了问题;间间断断的阅读和修改才终于将代码改正确了。我最初学C的时候曾经在某论坛某报纸某新闻中看到某某知名人士在毫无计算机基础的情况下学C语言只用了一个月就完全掌握了,然后立刻加入了某某开发团队开发出了某某大型的程序。如果粗俗一点我会对那家媒体说:你放屁。毕竟学编程不是一周不是一个月不是一年能学会的东西,它是需要靠长时间的积累和饱满的热情去对待的。所以当初学朋友看到类似以上媒体的言论请不要理会,他们只会误导你对学习编程的心态。
  我相信大牛门都是经过自己长时间积累才能被人们称为大牛的,如果仅靠一个月就成为大牛我觉得中国的软件水平该有多发达。可看看事实却是截然不同的,中国的软件业和软件水平也只是刚刚起步。举个简单的例子国外(欧美)有个Demo大赛,里面的高手用汇编语言写3D动画仅仅不到90K而且还带有声音。放眼中国,基本上没有人可以做到。他们有今天的成就完全是一种责任和态度,较完善的教育体制和教材,成熟的团队优秀的老师等等了。我不是崇洋媚外,只是说明白事实,我觉得人只有看到自己的不足才可能采取措施。天天盯着自己的有点看,那此人就得去精神病院逛逛了;人亦如此,国家也亦是如此。
  很感谢大家能看完这一堆的唠叨,我会陆续的发表新的内容。
  如果大家发现以上程序有问题,很感谢你能回复给我。我会虚心的接受,并仔细的思考和修改的。
  再次感谢大家。