[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home] [volume] [parte]


Capitolo 557.   Esempi di programmi con strutture di controllo

In questo capitolo vengono mostrati esempi di programmi estremamente banali, per dimostrare il funzionamento delle strutture di controllo, basate sostanzialmente su istruzioni di salto condizionato, attraverso l'aiuto di GDB (GNU debugger).(1)

Si dà per scontato che si sappiano compilare i programmi con GNU AS, oppure con NASM. Se si utilizza GNU AS, è bene ricordare di inserire l'opzione --gstabs, mentre con NASM è bene aggiungere l'opzione -g, in modo da poter gestire più facilmente GDB, disponendo dei riferimenti al sorgente:

as --gstabs -o nome.o nome.s[Invio]

nasm -g -f elf -o nome.o nome.s[Invio]

557.1   Somma attraverso l'incremento unitario

Viene mostrato un programma che esegue la somma di due valori interi senza segno, incrementando progressivamente il primo addendo, di una unità, corrispondentemente alla riduzione di una unità del secondo. Quando il secondo addendo è stato ridotto a zero, il primo contiene il risultato della somma. Il ciclo con cui si incrementa il primo addendo è controllato dall'istruzione LOOP:

# op1 + op2
#
.section .data
op1:    .int    0x00000007      # Intero senza segno.
op2:    .int    0x00000002      # Intero senza segno.
#
.section .text
.globl _start
#
_start:
    mov op1, %eax   # Primo addendo.
    mov op2, %ecx   # Secondo addendo.
bp1:
    cmp $0, %ecx    # Se il secondo addendo è zero, non esegue il ciclo
    je end_do_somma # di incrementi.
do_somma:
    inc %eax        # Aggiunge una unità a EAX.
    loop do_somma   # Ripete il ciclo se nel frattempo ECX non si è azzerato.
end_do_somma:
    mov %eax, %ebx  # Restituisce il valore ottenuto dalla somma.
    mov $1,   %eax  #
    int $0x80       #
; op1 + op2
;
section .data
op1:    dd      0x00000007      ; Intero senza segno.
op2:    dd      0x00000002      ; Intero senza segno.
;
section .text
global _start
;
_start:
    mov eax, [op1]  ; Primo addendo.
    mov ecx, [op2]  ; Secondo addendo.
bp1:
    cmp ecx, 0      ; Se il secondo addendo è zero, non esegue il ciclo
    je end_do_somma ; di incrementi.
do_somma:
    inc eax         ; Aggiunge una unità a EAX.
    loop do_somma   ; Ripete il ciclo se nel frattempo ECX non si è azzerato.
end_do_somma:
    mov ebx, eax    ; Restituisce il valore ottenuto dalla somma.
    mov eax, 1      ;
    int 0x80        ;

Come si può vedere, il ciclo di incremento è racchiuso tra le etichette do_somma e end_do_somma; inoltre, prima di entrare nel ciclo di somma, viene verificato che il secondo addendo sia diverso da zero, perché se è pari a zero, viene saltato il ciclo di somma, dal momento che EAX contiene già il valore corretto.

Viene mostrata una seconda versione del ciclo, dove si sostituisce l'istruzione LOOP con altre istruzioni di salto condizionato:

    cmp $0, %ecx    # Se il secondo addendo è zero, non esegue
    je end_do_somma # il ciclo di incrementi.
do_somma:
    inc %eax        # Aggiunge una unità a EAX.
    dec %ecx        # Riduce ECX di una unità.
    cmp $0, %ecx    # Se il secondo addendo è ancora diverso da zero,
    jnz do_somma    # allora ripete il ciclo.
end_do_somma:
    cmp ecx, 0      ; Se il secondo addendo è zero, non esegue
    je end_do_somma ; il ciclo di incrementi.
do_somma:
    inc eax         ; Aggiunge una unità a EAX.
    dec ecx         ; Riduce ECX di una unità.
    cmp ecx, 0      ; Se il secondo addendo è ancora diverso da zero,
    jnz do_somma    ; allora ripete il ciclo.
end_do_somma:

Viene mostrata una terza versione del ciclo, dove il controllo di uscita avviene solo all'inizio:

do_somma:
    cmp $0, %ecx    # Se il secondo addendo è zero, esce dal ciclo
    je end_do_somma # di incrementi.
    dec %ecx        # Riduce di una unità ECX.
    inc %eax        # Aggiunge una unità a EAX.
    jmp do_somma    # Ritorna all'inizio del ciclo.
end_do_somma:
do_somma:
    cmp ecx, 0      ; Se il secondo addendo è zero, esce dal ciclo
    je end_do_somma ; di incrementi.
    dec ecx         ; Riduce di una unità ECX.
    inc eax         ; Aggiunge una unità a EAX.
    jmp do_somma    ; Ritorna all'inizio del ciclo.
end_do_somma:

557.2   Moltiplicazione attraverso la somma

Viene mostrato un programma che esegue la moltiplicazione di due valori interi senza segno, sommando progressivamente il moltiplicando a un registro che inizialmente è pari a zero, corrispondentemente alla riduzione di una unità del moltiplicatore. Quando il moltiplicatore è stato ridotto a zero, il registro che viene incrementato contiene il risultato della moltiplicazione. Il ciclo è controllato dall'istruzione LOOP:

# op1 * op2
#
.section .data
op1:    .int    0x00000007      # Intero senza segno.
op2:    .int    0x00000003      # Intero senza segno.
#
.section .text
.globl _start
#
_start:
    mov op1, %eax        # Moltiplicando.
    mov op2, %ecx        # Moltiplicatore.
    mov $0, %ebx         # Risultato.
bp1:
    cmp $0, %ecx         # Se il moltiplicatore è zero,
    je end_do_moltiplica # non esegue il ciclo di somme.
do_moltiplica:
    add %eax, %ebx       # Aggiunge il moltiplicando al risultato.
    loop do_moltiplica   # Ripete il ciclo se nel frattempo
                         # ECX non si è azzerato.
end_do_moltiplica:
    mov $1,   %eax       # Restituisce il valore ottenuto
    int $0x80            # dalla moltiplicazione.
; op1 * op2
;
section .data
op1:    dd      0x00000007      ; Intero senza segno.
op2:    dd      0x00000003      ; Intero senza segno.
;
section .text
global _start
;
_start:
    mov eax, [op1]       ; Moltiplicando.
    mov ecx, [op2]       ; Moltiplicatore.
    mov ebx, 0           ; Risultato.
bp1:
    cmp ecx, 0           ; Se il moltiplicatore è zero,
    je end_do_moltiplica ; non esegue il ciclo di somme.
do_moltiplica:
    add ebx, eax         ; Aggiunge il moltiplicando al risultato.
    loop do_moltiplica   ; Ripete il ciclo se nel frattempo
                         ; ECX non si è azzerato.
end_do_moltiplica:
    mov eax, 1           ; Restituisce il valore ottenuto
    int 0x80             ; dalla moltiplicazione.

Viene mostrata una seconda versione del ciclo, dove si sostituisce l'istruzione LOOP con altre istruzioni di salto condizionato:

    cmp $0, %ecx         # Se il moltiplicatore è zero,
    je end_do_moltiplica # non esegue il ciclo di somme.
do_moltiplica:
    add %eax, %ebx       # Aggiunge il moltiplicando al risultato.
    cmp $0, %ecx         # Se il moltiplicatore è ancora diverso
    jnz do_moltiplica    # da zero, allora ripete il ciclo.
end_do_moltiplica:
    cmp ecx, 0           ; Se il moltiplicatore è zero,
    je end_do_moltiplica ; non esegue il ciclo di somme.
do_moltiplica:
    add ebx, eax         ; Aggiunge il moltiplicando al risultato.
    cmp ecx, 0           ; Se il moltiplicatore è ancora diverso
    jnz do_moltiplica    ; da zero, allora ripete il ciclo.
end_do_moltiplica:

Viene mostrata una terza versione del ciclo, dove il controllo di uscita avviene solo all'inizio:

do_moltiplica:
    cmp $0, %ecx         # Se il moltiplicatore è zero,
    je end_do_moltiplica # esce dal ciclo di somme.
    dec %ecx             # Riduce di una unità il moltiplicatore.
    add %eax, %ebx       # Aggiunge il moltiplicando al risultato.
    jmp do_moltiplica    # Ritorna all'inizio del ciclo.
end_do_moltiplica:
do_moltiplica:
    cmp ecx, 0           ; Se il moltiplicatore è zero,
    je end_do_moltiplica ; esce dal ciclo di somme.
    dec ecx              ; Riduce di una unità il moltiplicatore.
    add ebx, eax         ; Aggiunge il moltiplicando al risultato.
    jmp do_moltiplica    ; Ritorna all'inizio del ciclo.
end_do_moltiplica:

557.3   Divisione attraverso la sottrazione

Viene mostrato un programma che esegue la divisione di due valori interi senza segno, sottraendo progressivamente il divisore a un registro che inizialmente è pari al valore del dividendo, corrispondentemente all'incremento di una unità del risultato (a partire da zero). Quando il registro che viene ridotto, di volta in volta, del valore del divisore, diventa minore del divisore, la divisione termina. Viene proposta una sola versione, con un ciclo controllato da una condizione iniziale:

# op1 / op2
#
.section .data
op1:    .int    33      # Intero senza segno.
op2:    .int    12      # Intero senza segno.
#
.section .text
.globl _start
#
_start:
    mov op1,  %eax       # Dividendo.
    mov op2,  %ecx       # Divisore.
    mov $0,   %ebx       # Risultato.
    mov %eax, %edx       # Resto.
do_dividi:
    cmp %ecx, %edx       # Se il resto è minore del divisore,
    jb end_do_dividi     # conclude il ciclo di sottrazioni.
    sub %ecx, %edx       # Sottrae al resto il divisore.
    inc %ebx             # Incrementa il risultato di una unità.
    jmp do_dividi        # Torna all'inizio del ciclo.
end_do_dividi:
    mov $1,   %eax       # Restituisce il valore ottenuto
    int $0x80            # dalla moltiplicazione.
; op1 / op2
;
section .data
op1:    dd      33      ; Intero senza segno.
op2:    dd      12      ; Intero senza segno.
;
section .text
global _start
;
_start:
    mov eax, [op1]       ; Dividendo.
    mov ecx, [op2]       ; Divisore.
    mov ebx, 0           ; Risultato.
    mov edx, eax         ; Resto.
do_dividi:
    cmp edx, ecx         ; Se il resto è minore del divisore,
    jb end_do_dividi     ; conclude il ciclo di sottrazioni.
    sub edx, ecx         ; Sottrae al resto il divisore.
    inc ebx              ; Incrementa il risultato di una unità.
    jmp do_dividi        ; Torna all'inizio del ciclo.
end_do_dividi:
    mov eax, 1           ; Restituisce il valore ottenuto
    int 0x80             ; dalla moltiplicazione.

557.4   Elevamento a potenza

Viene mostrato un programma che calcola la potenza di due numeri interi senza segno, moltiplicando progressivamente la base, corrispondentemente al decremento di un contatore che parte dal valore dell'esponente. Quando il contatore raggiunge lo zero, il ciclo di moltiplicazioni termina e il risultato della potenza viene emesso come valore di uscita, ammesso che sia abbastanza piccolo da poter essere rappresentato:

# op1 / op2
#
.section .data
op1:    .int    5       # Intero senza segno.
op2:    .int    3       # Intero senza segno.
#
.section .text
.globl _start
#
_start:
    mov op1,  %esi       # Base.
    mov op2,  %edi       # Esponente.
    mov $0,   %edx       # Risultato: EDX:EAX
    mov $1,   %eax       #
    mov %edi, %ecx       # Contatore.
do_potenza:
    cmp $0, %ecx         # Se il contatore ha raggiunto lo zero,
    jz end_do_potenza    # conclude il ciclo di moltiplicazioni.
    mul %esi             # EDX:EAX := EAX*ESI.
    dec %ecx             # Riduce di una unità il contatore.
    jmp do_potenza       # Torna all'inizio del ciclo.
end_do_potenza:
    mov %eax, %ebx       # Restituisce il valore della potenza,
    mov $1,   %eax       # ammesso che sia abbastanza piccolo
    int $0x80            # da poter essere rappresentato come
                         # valore di uscita.
; op1 / op2
;
section .data
op1:    dd      5       ; Intero senza segno.
op2:    dd      3       ; Intero senza segno.
;
section .text
global _start
;
_start:
    mov esi, [op1]       ; Base.
    mov edi, [op2]       ; Esponente.
    mov edx, 0           ; Risultato: EDX:EAX
    mov eax, 1           ;
    mov ecx, edi         ; Contatore.
do_potenza:
    cmp ecx, 0           ; Se il contatore ha raggiunto lo zero,
    jz end_do_potenza    ; conclude il ciclo di moltiplicazioni.
    mul esi              ; EDX:EAX := EAX*ESI.
    dec ecx              ; Riduce di una unità il contatore.
    jmp do_potenza       ; Torna all'inizio del ciclo.
end_do_potenza:
    mov ebx, eax         ; Restituisce il valore della potenza,
    mov eax, 1           ; ammesso che sia abbastanza piccolo
    int 0x80             ; da poter essere rappresentato come
                         ; valore di uscita.

557.5   Moltiplicazione attraverso lo scorrimento e la somma

Viene mostrato un programma che calcola il prodotto di due numeri interi senza segno, sommando progressivamente il moltiplicando che viene fatto scorrere verso sinistra. Per comprendere il procedimento occorre fare mente locale al modo in cui la moltiplicazione verrebbe eseguita a mano: il moltiplicando viene sommato al risultato (che inizialmente è pari a zero) se la cifra meno significativa del moltiplicatore è pari a uno; successivamente il moltiplicando viene fatto scorrere verso sinistra di una posizione e lo si somma nuovamente al risultato se la cifra successiva del moltiplicatore è pari a uno; quindi si continua fino a che si esauriscono le cifre del moltiplicatore.

Nel programma mostrato, durante il ciclo di somme, il moltiplicatore viene fatto scorrere verso destra, in modo da poter verificare il valore della cifra espulsa attraverso l'indicatore di riporto (carry). Così facendo, quando il moltiplicatore è pari a zero, il ciclo di somme può terminare.

# op1 * op2
#
.section .data
op1:    .byte   0x07, 0x00 # little endian = 0x0007  intero senza segno.
op2:    .byte   0x03, 0x00 # little endian = 0x0003  intero senza segno.
#
.section .text
.globl _start
#
_start:
    movzx op1,  %edx       # Moltiplicando.
    movzx op2,  %ecx       # Moltiplicatore.
    mov   $0,   %eax       # Risultato.
do_mol:
    cmp   $0,   %ecx       # Se il moltiplicatore è pari a zero, il ciclo
    jz    end_do_mol       # deve terminare.
    shr   $1,   %ecx       # Fa scorrere a destra il moltiplicatore e se
    jnc   end_do_mol_somma # l'indicatore di riporto non è attivo, salta
                           # la somma.
do_mol_somma:
    add   %edx, %eax       # Aggiunge il moltiplicando al risultato.
end_do_mol_somma:    
    shl   $1,   %edx       # Fa scorrere il moltiplicando a sinistra.
    jmp   do_mol           # Torna all'inizio del ciclo.
end_do_mol:
    mov   %eax, %ebx       # Restituisce il valore del prodotto,
    mov   $1,   %eax       # ammesso che sia abbastanza piccolo
    int   $0x80            # da poter essere rappresentato come
                           # valore di uscita.
; op1 * op2
;
section .data
op1:    dw      0x0007     ; Intero senza segno.
op2:    dw      0x0003     ; Intero senza segno.
;
section .text
global _start
;
_start:
    movzx edx, word [op1]  ; Moltiplicando.
    movzx ecx, word [op2]  ; Moltiplicatore.
    mov   eax,      0      ; Risultato.
do_mol:
    cmp   ecx,      0      ; Se il moltiplicatore è pari a zero, il ciclo
    jz    end_do_mol       ; deve terminare.
    shr   ecx,      1      ; Fa scorrere a destra il moltiplicatore e se
    jnc   end_do_mol_somma ; l'indicatore di riporto non è attivo, salta
                           ; la somma.
do_mol_somma:
    add   eax,      edx    ; Aggiunge il moltiplicando al risultato.
end_do_mol_somma:    
    shl   edx,      1      ; Fa scorrere il moltiplicando a sinistra.
    jmp   do_mol           ; Torna all'inizio del ciclo.
end_do_mol:
    mov   ebx,      eax    ; Restituisce il valore del prodotto,
    mov   eax,      1      ; ammesso che sia abbastanza piccolo
    int   0x80             ; da poter essere rappresentato come
                           ; valore di uscita.

Come si può vedere, moltiplicando e moltiplicatore sono variabili da 16 bit, in modo da avere la certezza che il risultato del prodotto sia contenibile in un registro. Nel caso del primo listato, fatto per GNU AS, lo spazio in memoria viene dichiarato come sequenza di due byte, perché manca la possibilità di definire esplicitamente un intero «corto».

557.6   Conteggio dei bit a uno

Viene proposto un esempio di programma che conta quanti bit a uno compongono un certo valore numerico. Per farlo, si usa lo scorrimento (in questo caso è a destra, ma non farebbe differenza) e quindi viene contato il riporto (l'indicatore di riporto è attivo se fuoriesce una cifra a uno).

#
.section .data
op1:      .int 44
#
.section .text
.globl _start
#
_start:
    mov   op1, %eax         # EAX contiene il numero di cui contare i bit a 1.
    mov   $0, %ecx          # ECX è il contatore dei bit a uno.
do_conta:
    cmp   $0, %eax          # Se EAX è a zero, il conteggio dei bit
    jz    end_do_conta      # si conclude.
    shr   $1, %eax          # Fa scorrere a destra EAX.
    adc   $0, %ecx          # ECX = ECX + 0 + carry.
    jmp   do_conta          # Riprende il ciclo.
end_do_conta:
    mov   %ecx, %ebx        # Restituisce la quantità di bit.
    mov   $1, %eax          #
    int   $0x80             #
;
section .data
op1:      dd 44
;
section .text
global _start
;
_start:
    mov   eax, [op1]        ; EAX contiene il numero di cui contare i bit a 1.
    mov   ecx, 0            ; ECX è il contatore dei bit a uno.
do_conta:
    cmp   eax, 0            ; Se EAX è a zero, il conteggio dei bit
    jz    end_do_conta      ; si conclude.
    shr   eax, 1            ; Fa scorrere a destra EAX.
    adc   ecx, 0            ; ECX = ECX + 0 + carry.
    jmp   do_conta          ; Riprende il ciclo.
end_do_conta:
    mov   ebx, ecx          ; Restituisce la quantità di bit.
    mov   eax, 1            ;
    int   0x80              ;

Viene proposto un metodo alternativo che utilizza la sottrazione e l'abbinamento con l'operatore logico AND (è descritto nella sezione 549.10):

#
.section .data
op1:      .int 44
#
.section .text
.globl _start
#
_start:
    mov   op1, %eax     # EAX contiene il numero di cui contare i bit a 1.
    mov   $0, %ecx      # ECX è il contatore dei bit a uno.
do_conta:
    cmp   $0, %eax      # Se EAX è a zero, il conteggio dei bit
    jz    end_do_conta  # si conclude.
    mov   %eax, %edx    # Fa una copia in EDX.
    dec   %eax          # Decrementa EAX di una unità.
    and   %edx, %eax    # EAX := EAX AND EDX.
    inc   %ecx          # ECX++
    jmp   do_conta      # Riprende il ciclo.
end_do_conta:
    mov   %ecx, %ebx    # Restituisce la quantità di bit.
    mov   $1, %eax      #
    int   $0x80         #
;
section .data
op1:      dd 44
;
section .text
global _start
;
_start:
    mov   eax, [op1]    ; EAX contiene il numero di cui contare i bit a 1.
    mov   ecx, 0        ; ECX è il contatore dei bit a uno.
do_conta:
    cmp   eax, 0        ; Se EAX è a zero, il conteggio dei bit
    jz    end_do_conta  ; si conclude.
    mov   edx, eax      ; Fa una copia in EDX.
    dec   eax           ; Decrementa EAX di una unità.
    and   eax, edx      ; EAX := EAX AND EDX.
    inc   ecx           ; ECX++
    jmp   do_conta      ; Riprende il ciclo.
end_do_conta:
    mov   ebx, ecx      ; Restituisce la quantità di bit.
    mov   eax, 1        ;
    int   0x80          ;

1) GDB   GNU GPL


Appunti di informatica libera 2008 --- Copyright © 2000-2008 Daniele Giacomini -- <appunti2 (ad) gmail·com>


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome esempi_di_programmi_con_strutture_di_controllo.htm

[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home]

Valid ISO-HTML!

CSS validator!

Gjlg Metamotore e Web Directory