【问题标题】:Finding the minimum in an array (Intel8086)查找数组中的最小值(Intel8086)
【发布时间】:2016-03-26 12:32:26
【问题描述】:

我想编写一个简单的程序来查找数组中的最小值。我使用的是 Intel 8086 架构(如果我是对的?)。问题是我对汇编语言完全陌生,而且我只是无法弄清楚我在代码中做错了什么,我被卡住了。

最后(退出前)我的ax 寄存器不包含我的结果。
恐怕它甚至没有把它放在那里(我用 Turbo Debugger 工具检查寄存器的值)。

.MODEL TINY

Data SEGMENT
                        arr_len     EQU 5
                        arr         DB 07h, 02h, 03h, 10h, 12h
                        minimum     DB 255
Data ENDS

Code SEGMENT
                        ORG 100h
                        ASSUME CS:Code, DS:Data, SS:Code
Start:
                        mov ax, SEG Data
                        mov ds, ax  ;load data to ds reg    
                        mov cx, arr_len ;arr_length to counter
                        mov bx, OFFSET arr ;load first elem.
Search: 
                        inc bx
                        cmp bl, [minimum] ;compare loaded elem with min
                        jb Swap ;if bl<minimum
Swap:
                        mov minimum, bl
                        loop Search         

                        mov al, minimum ;result here?
                        mov ax, 4C00h
                        int 21h

Code ENDS
END Start

谁能给我建议这里有什么问题?提前致谢。

【问题讨论】:

  • 你总是交换,即使当前元素不是最小值。
  • @user786653,我明白了,我将loop 指令替换为jb 的正下方。现在它应该 Swap 只有当我发现价值较小的时候,对吧?或者我还是不明白...
  • CMP BL, [minimum] 比较的是 BL 中的值,而不是您想要的 arr 中的值。你永远不会从数组中加载任何值,这就是为什么你没有得到你想要的结果。而是将 AL 设置为 255,将 [BX] 的值与 AL 进行比较,然后从那里开始。

标签: assembly x86-16 tasm


【解决方案1】:
  • 微型模型不需要显式设置 DS。 (CS=DS=ES=SS)
  • 由于 BX 第一次指向您的阵列,您不应该立即增加它!仅在循环之前执行inc
  • 您真正比较的不是数组元素,而是您迭代数组的地址的低字节。
  • 如果之后直接在 AX 中输入 4C00h 退出,则将任何结果放入 AL 是没有意义的。不过,使用调试器查看 AL 是可行的。

这是一个可以工作的版本:

Search: 
    mov al, [bx]
    cmp al, [minimum] ;compare loaded elem with min
    jnb NoSwap        ;if al>=minimum
    mov [minimum], al ;New minimum
NoSwap:
    inc bx
    loop Search

【讨论】:

  • 我怀疑他打算将最小值作为 int 21/ah=4ch 返回码返回(可以在 DOS 批处理文件中检查)。我认为 mov ax, 4C00h 应该是 mov ah, 4Ch,因为我认为预期的行为应该是。
  • 非常感谢,我明白我做错了什么,这很明显。我还决定将我的最小值存储在dl 寄存器而不是al 中,以便使用调试器可以清楚地看到它。它在大气压下工作。我很感激!
【解决方案2】:

就像 Fifoernik 的回答所解释的那样,无论是否被采用,您的条件分支都会跳转到同一个地方,因为分支目标是下一个 insn。


这里是如何做到这一点,而不是在你积累它时将你的 minimum 保留在内存中,因为那太可怕了。

此版本在al 中累积minimum,从不将其存储到内存中。我还使用字符串操作来取乐。您将获得更小的代码大小,但可能比使用 mov 加载的代码要慢。使用si 作为源指针是一个很好的约定,可以帮助人类跟踪事物,即使不使用字符串操作也是如此。当然,不要仅仅为此而使代码变得更糟,但是当你有选择的时候,我推荐它。

我也避免使用loop,因为it's slow and wasn't gaining us much of anything。除非您要针对代码大小而非速度进行核心优化,否则不要使用loop

.MODEL TINY
Data SEGMENT
    arr         DB 07h, 02h, 03h, 10h, 12h
    arr_len     EQU $-arr                   ; let the assembler calculate the size to avoid mistakes
Data ENDS

Code SEGMENT
    ORG 100h
    ASSUME CS:Code, DS:Data, SS:Code
unsigned_min:
    ;; segment setup not needed with tiny model, according to Fifoernik
    ; assume arr_len >= 2
    mov    si, OFFSET arr       ; src pointer = &first element
    lea    di, [si + arr_len]   ; end pointer.
    ;; mov di, arr_len + OFFSET arr ; also works, since input pointer is static
    ;; or use arr_len + OFFSET arr as an immediate operand for the loop condition, if you don't care about keeping the hard-coded input address out of the loop itself.

    cld                         ; clear the direction flag so string insns count upwards.  Omit if the ABI guarantees this state already
    lodsb                       ; al = min so far = first element, and advance si to point to the second element


;; on entry: AL = 1st element (min).  SI = pointer to 2nd element.  DI = end-pointer
Search:
    cmpsb                       ; compare [si] with current min (al), and ++si
    jae   .no_new_min

    mov    al, [si-1]           ; conditionally skipped.  You could use `cmov` instead of the branch on a CPU supporting 686 insns
.no_new_min
    cmp    si, di               ; loop while si < end
    jb    Search

    ; min is in AL

    mov ah, 4Ch            ; exit with AL as exit status
    int 21h

如果您不能假设arr_len &gt;= 2,则将 min 初始化为 255 或第一个元素,然后进入循环,使其指向第一个元素,而不是第二个。或者在循环外使用额外的cmp si,di /jb

【讨论】:

  • mov ah, 4Ch int 21h 是用于退出程序的 DOS 中断。 AL 恰好是返回给 DOS 的返回值。我只是删除您的最终评论;; make an exit syscall, or ret if this is a function,因为您的代码已经做到了。 RET 也会起作用,因为 COM 程序在程序启动时将 0x0000 推入堆栈。 PSP:0x0000 包含将退出程序的int 20h 指令(CP/M 兼容性)。如果这是一个 DOS EXE 程序,ret 将无法工作(首选 int 21h/ah=4ch)。
  • 由于org 100h.MODEL TINY,可以看出这是一个COM 程序。在这种情况下 CS=DS=SS。前 100h 字节包含 PSP(程序段前缀)。 COM 程序入口点总是在 DOS 加载 COM 程序的段开始的 100h 处。
  • 当然你使用ret在这种情况下你实际上不能用那个方法返回一个值,所以我建议只用mov ah, 4Chint 21h,其中AL(return值)=最小值
  • @MichaelPetch:感谢您澄清这一点。 DOS 系统调用部分对我来说几乎完全没有兴趣,所以我什至没有费心去查找那个未注释的系统调用是什么。我认为这是某种打印整数。我已经多次说过 DOS 和 16 位通常已经过时,不值得学习。我希望我没有在了解合法的 16 位寻址模式上浪费脑力,但人们总是在 SO 上问 16 位问题。
  • @MateuszKwasniak:是的,我知道 DOS 使用int 21h 进行系统调用,调用号码在AH。你做对了。只是DOS已经过时了,不值得学习,除非有人强迫你这样做,这就是为什么我只对程序的循环部分感兴趣,而不是最后的系统调用。
猜你喜欢
  • 2018-09-24
  • 2021-09-15
  • 2015-04-09
  • 1970-01-01
  • 2018-05-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多