Отслеживание программы сборки NCR MASM

Я пропустил сессию, когда наш лектор объяснил это.

Я знаю формулы для NCR

NCR = N!/(R! * (N-R)!)

Я не выполняю NCR PROC, так как факториал не найден, и там выполняются некоторые жуткие рекурсивные задания (% _%)..


print macro str
    lea dx,msg
    mov ah,09h
    int 21h
endm

.model small
.data
    n dw 15
    r dw 6
    ncr dw 1
    msg db "The NCR is : ","$"
.code
     start: mov ax,@data
            mov ds,ax

            mov bx,n
            mov cx,r
            inc bx
            call ncr1
            print msg          
            mov ax,ncr
            call display

            mov ah,4ch
            int 21h

          ncr1 proc
                cmp cx,00      
                  je l1
                push cx         
                dec cx
                call ncr1        
                mov ax,bx       
                pop cx          
                sub ax,cx       
                mul ncr         
                div cx          
                mov ncr,ax      
              l1 : ret
          ncr1 endp

          display proc
                aam
                add ax,3030h
                mov bx,ax
                mov dl,bh
                mov ah,02h
                int 21h
                mov dl,bl
                mov ah,02h
                int 21h                
             ret
      display endp

end start                

### РЕДАКТИРОВАНИЕ ###
Я действительно получил эти коды от одного из моих одноклассников, которые присутствовали на сессии...
Есть некоторые ошибки с кодом... Предложите способ выполнения NCR с ассемблером или решить эту ошибку...

Ответ 1

Хорошо, я вижу некоторое значение повторного использования, показывающее, как анализировать куски кода ассемблера, так что вот оно.


Мы имеем дело с подпрограммой здесь, куском кода, который должен быть относительно автономным.

Итак, сначала определим необработанный эффект, который он имеет в программе: его входы, выходы и эффекты на sp - то есть его вызывающее соглашение и подпись.

Какие объекты используются, которые не установлены им?

ncr1 proc
    cmp cx,00    # <-- cx
      je l1
    push cx
    dec cx
    call ncr1
    mov ax,bx    # <--bx
    pop cx
    sub ax,cx
    mul ncr      # <--ncr
    div cx
    mov ncr,ax
  l1 : ret
ncr1 endp

Какие объекты он модифицирует и не восстанавливает впоследствии?

ncr1 proc
    cmp cx,00
      je l1
    push cx      # cx->local_stack[-2]
    dec cx       # -->cx? (overridden)
    call ncr1
    mov ax,bx
    pop cx       # local_stack[-2]->cx => cx restored
                 # (the next step is actually needed to deduce push/pop
                 # correspondence so they should be done in parallel)
    sub ax,cx
    mul ncr      # -->ax,dx
    div cx
    mov ncr,ax   # -->ncr
  l1 : ret
ncr1 endp

Только последние изменения в объектах отмечены (поскольку они преобладают над более ранними). ​​

Имеет ли какое-либо чистое влияние на sp? (цифры являются текущими sp по отношению к обратному адресу)

ncr1 proc
    cmp cx,00
      je l1
    push cx     #-2
    dec cx
    call ncr1   #delta is same as self
    mov ax,bx
    pop cx      #0
    sub ax,cx
    mul ncr
    div cx
    mov ncr,ax
  l1 : ret      #without an argument, only cleans the return address
ncr1 endp

Это не так (так "то же самое, что и я" - 0), а 0 во всех случаях в ret подтверждает правильность правильного локального стека.

В заключение, его подпись:

ncr1 (cx,bx,ncr) -> ax,dx,ncr

Где ax и dx, вероятно, не используются (но они по-прежнему volatile). И вызывающая конвенция пользовательский чистый регистр с одним жестко запрограммированным параметром /out.


Теперь все, что уходит, это отслеживать, какое физическое - и затем - концептуальное - значение, которое имеет каждая сущность в любое время:

ncr1 proc  --initially: bx=N, cx=R (metavariables)
    cmp cx,00    # if R==0:
      je l1      #   ret (ax,dx,ncr unchanged)
    push cx      #[-2]=R
    dec cx       #cx=R-1
    call ncr1    #ncr1(N,R-1) -> ax,dx,ncr(probably the result)
    mov ax,bx    #ax=N
    pop cx       #cx=[-2]=R
    sub ax,cx    #ax=N-R
    mul ncr      #dx:ax=(N-R)*ncr = (N-R)*ncr1(N,R-1)
    div cx       #ax=[(N-R)*ncr1(N,R-1)]/R
    mov ncr,ax   #ncr=[(N-R)*ncr1(N,R-1)]/R
  l1 : ret
ncr1 endp        # -> ax,dx,ncr(the result, now we're sure)

Здесь это: процедура вычисляет (N,R) -> [(N-R)*ncr1(N,R-1)]/R, где N=bx, R=cx и результат ncr (который мутирован).

Что выглядит подозрительно: (N-R) должно быть (N+1-R) в канонической формуле согласно comment62556150. Если мы заменим n=N-1, он станет следующим: (n+1,R) -> [(n+1-R)*ncr(n+1,R-1)]/R, который выглядит нормально (первый аргумент никогда не изменяется)... поэтому процедура фактически вычисляет nCr(n-1,r).

Выбор перехода n+1 должен быть из-за того, что n переходит только в формулу как n+1, поэтому этим мы сохраняем циклы при каждом увеличении его.