You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

68000双遍汇编子程序:数组均值计算及技术评审问询

68000子程序:数组均值计算与大于均值元素计数的代码评审

背景

为练习编写了一个68000子程序,实现以下功能:

  1. 计算n元素有符号字数组A的整数均值;
  2. 统计数组A中严格大于该均值的元素数量。

所有参数(&A、&AvgRes、&CountGT、n)需通过栈传递;主程序入参、调用子程序后一次性清理栈。

测试数据验证程序可正常运行(均值=1,计数=5),现寻求对代码清晰度、健壮性及68k编程惯例的评审。


代码(可在Easy68k中运行)

ORG    $8000
; =================================================================
; AvgAndCount  –  integer average of n signed words in A
;                 + count of elements greater than that average
; Parameters (below return addr):
;   4(A7)  = &A        (long)
;   8(A7)  = &AvgRes   (long)
;  12(A7)  = &CountGT  (long)
;  16(A7)  = n         (word)
; =================================================================

VectorA    DC.W  2,-3,4,-1,0,5,-7,8,-2,6   ; test set (10 words)
AvgRes     DS.W  1                         ; average → here
CountGT    DS.W  1                         ; count   → here
LEN        EQU   10

; ---------------- main -------------------------------------------------
START:
    MOVE.W  #LEN,-(A7)          ; push n               (word)
    PEA.L   CountGT             ; push &CountGT        (long)
    PEA.L   AvgRes              ; push &AvgRes         (long)
    PEA.L   VectorA             ; push &A              (long)
    BSR.S   AvgAndCount         ; call routine
    ADDA.L  #14,A7              ; pop 3 longs + 1 word
    SIMHALT

; ---------------- AvgAndCount -----------------------------------------
; Pass 1: accumulate 32-bit sum  →  avg = sum / n
; Pass 2: count elements that are > avg
; Uses: A0, A1, D0–D3, D6, D7
; ----------------------------------------------------------------------
AvgAndCount:
    ; ----- Pass 1 – sum ----------------------------------------------
    MOVE.W   16(A7),D7          ; D7 = n
    SUBQ.W   #1,D7              ; DBRA counter needs n-1
    MOVEA.L   4(A7),A0          ; A0 = &A
    CLR.L     D0                ; 32-bit accumulator

SumLoop:
    ADD.W   (A0)+,D0            ; add next word to long sum
    DBRA    D7,SumLoop

    ; ----- integer average = sum / n ---------------------------------
    MOVE.W  16(A7),D3           ; D3 = n
    MOVE.W  D3,-(A7)            ; push divisor (DIVS needs mem op)
    DIVS    (A7),D0             ; 32 ÷ 16  ⇒  quotient in D0.low
    ADDQ.L  #2,A7               ; pop divisor
    MOVE.W  D0,D1               ; D1 = avg
    MOVEA.L  8(A7),A1
    MOVE.W  D1,(A1)             ; save avg in AvgRes

    ; ----- Pass 2 – count > avg --------------------------------------
    MOVE.W  16(A7),D7
    SUBQ.W  #1,D7
    MOVEA.L  4(A7),A0           ; reset A pointer
    CLR.W    D2                 ; counter = 0

CntLoop:
    MOVE.W  (A0)+,D6
    CMP.W   D1,D6               ; A[i] > avg ?
    BLE.S   Skip
    ADDQ.W  #1,D2               ; ++counter
Skip:
    DBRA    D7,CntLoop

    ; ----- store count -----------------------------------------------
    MOVEA.L 12(A7),A1
    MOVE.W  D2,(A1)
    RTS
          END  START

技术问询解答

1. 正确性与溢出:当前用D0.L存储10元素的和是安全的,若n更大是否应改用64位(D0:D1)存储?

是的,必须切换到64位累加。单个有符号字的范围是-3276832767,32位有符号长字的范围是-21474836482147483647。当n超过65535,或者数组元素普遍接近极值时,32位累加必然溢出。

68000支持64位累加操作,实现方式是用D0:D1组成双寄存器:

  • 初始时CLR.L D0、CLR.L D1清零;
  • 每次循环执行ADD.W (A0)+,D0,再执行ADDX.L #0,D1处理进位(ADDX会把之前ADD产生的进位加到D1中);
  • 后续除法若需处理64位被除数,可先用EXT.L D3把n扩展为32位,再用DIVL指令(68020及以上支持),或手动实现64÷16的除法逻辑。

2. DIVS用法:将除数压栈是实现32÷16除法最简洁高效的方式吗?还是将n存入静态内存更好?

压栈是简洁的实现方式,但不是最高效的——栈操作涉及内存读写。更高效的方案是利用栈帧临时单元:用LINK A6,#-2建立栈帧,把n存入-2(A6),然后用DIVS -2(A6),D0,最后用UNLK A6清理栈帧。这种方式避免了额外的栈push/pop操作,且保持子程序的可重入性。

绝对不推荐用静态内存:静态变量是全局的,会导致子程序无法重入,不符合模块化编程的惯例,在多任务或递归场景下会出问题。

3. 可读性与注释:现有的头部块及行内注释是否足以让其他阅读者理解代码?

现有注释已经达标,但可以做以下优化提升可读性:

  • 修正参数列表里的HTML转义字符,把&A改成&A,更直观;
  • 在SumLoop和CntLoop前增加一句注释,明确循环的核心作用(比如; 遍历数组,累加所有元素到32位寄存器D0);
  • 给DIVS操作加注释说明:; DIVS将32位被除数D0除以16位除数,商存在D0低16位,余数在高16位;
  • 在寄存器使用说明里,明确标注哪些寄存器会被修改(比如; 破坏寄存器:A0,A1,D0-D3,D6,D7),方便调用者提前保存需要保留的寄存器。

4. 经典优化点:是否遗漏了经典优化手段(如向上取整、无需复杂除法的单遍计数等)?

(1)单遍遍历优化

无法实现真正的单遍遍历:因为均值依赖总和,必须先遍历一次计算总和才能得到均值,所以两次遍历是必要的。不过可以优化数组指针的重置:如果在Pass1结束时把A0的初始地址存在另一个寄存器(比如A2),Pass2就不用重新从栈里加载地址,直接用MOVEA.L A2,A0即可,减少一次内存访问。

(2)整数均值的向上取整

当前代码用DIVS实现的是向零截断的除法,对于负总和且有余数的情况,结果会偏离数学上的整数均值(比如总和-5,n=2,-5/2=-2,但实际均值应该是-3)。可以增加余数判断修正:

; 在DIVS之后添加
TST.W D0          ; 检查商的符号
BPL.S NoAdjust    ; 正数或零,无需调整
TST.W D0+2        ; 检查余数是否非零
BEQ.S NoAdjust
SUBQ.W #1,D1      ; 余数非零的负数,均值减1实现向负无穷取整
NoAdjust:

(3)循环效率优化

你已经用到了68000高效的DBRA循环指令,这是最优选择,无需替换。另外,Pass2里的CLR.W D2可以保持,比SUBQ.W #0,D2更直观。


内容的提问来源于stack exchange,提问作者Pato

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 17:30:56