PRESENTAZIONE DEL CORSO
Questo corso vuole introdurre la programmazione attraverso uno dei linguaggi più vicini all'hardware che conosciamo, questo linguaggio prende il nome di Assembly.
Una delle ragioni per cui un programmatore dovrebbe conoscere almeno le basi di questo linguaggio è rappresentata dalla possibilità di approfondire maggiormente la conoscenza di come i computer funzionano, di come il processore riesce ad eseguire le istruzioni in linguaggio macchina e come sono rappresentati i dati all'interno della memoria e dell'hard disk. Inoltre non dimentichiamo che, in passato, molte funzioni appartenenti a giochi come DOOM sono state sviluppate in questo linguaggio in quanto molto più veloci delle stesse sviluppate in C.
Spesso le persone confondono il termine Assembler con l'Assembly, quest'ultimo è il linguaggio di programmazione mentre l'Assembler è il programma che trasforma l'Assembly in linguaggio macchina (molto simile all'Allassembly). In linguaggio macchina sono definite l'insieme di istruzioni fondamentali che un processore è in grado di eseguire.
PANORAMICA SULLA FAMIGLIA DI PROCESSORI x86
La famiglia x86 è nata nel 1978 grazie ad Intel con il processore 8086 (16-bit) evoluzione dell' 8080 e 8085 (8-bit) e sostituito
in seguito dall'8088 e si è evoluta fino alla linea Pentium e agli attuali processori multi core (dual-core, quad-core, ecc).
Tutti questi processori preservano la compatibilità con i precedenti, ciò significa che è possibile far girare un programma su un processore recente anche se questo risulta essere stato scritto per un processore 8086.
Oggi rimangono ben pochi computer in uso, dotati di processori (CPU) 8086, 8088, 80286, 80386 (32-bit), 80486 poiché si tratta di computer obsoleti che non riescono ad eseguire i sistemi operativi e software più recenti. Anche i computer dotati di processore 80586/Pentium stanno ormai diminuendo, mano a mano che il software diviene sempre più esigente in termini di risorse e velocità lasciando sempre più spazio ai nuovi processori multi core.
Per chi volesse approfondire la storia dei processori consigliamo la pagina di Wikipedia: architettura_x86
SISTEMI NUMERICI
Prima di cominciare a capire come programmare in Assembly è opportuno cercare di comprendere come i numeri sono rappresentati all'interno del processore (da adesso chiamata CPU).
Noi siamo abituati a contare da 0 (zero) a 9, in tutto utilizziamo 10 cifre (0,1,2,3,4,5,6,7,8,9), come notò Aristotele forse perché abbiamo dieci dita nelle mani e nei piedi, e per questo il nostro sistema numerico viene chiamato sistema decimale o sistema posizionale in base 10. Perché posizionale? Immaginiamo di avere un numero come 9876. La posizione di ogni cifra ha un significato preciso: 9876 = (9*1000)+(8*100)+(7*10)+(6*1) dove 1000 è pari alla 10^3 (dove ^3 sta per elevato alla terza), 100 è pari alla 10^2, 10 è pari alla 10^1 e 1 è pari alla 10^0. Le potenze (terza, seconda, prima e zero) indicano proprio la posizione del numero partendo da 0.
Il computer utilizza un sistema numerico molto più semplice formato solamente da due cifre: 0 e 1. Questo sistema prende il nome di sistema binario o sistema posizionale in base 2. Per contare da 0 a 5 il computer conterebbe in questo modo: 0, 1, 10, 11, 100, 101, che nel sistema decimale corrispondono a 0,1,2,3,4,5. Risulta subito chiaro che utilizzare un sistema di questo tipo per noi non è molto comodo, anzi alquanto complicato, ma per un computer il sistema binario è perfetto in quanto le cifre 0 e 1 rappresentano perfettamente gli stati di un interruttore (acceso, spento) che sono i componenti elettronici (transistor) che costituiscono le CPU e le memorie.
L'ultimo sistema numerico che ci interessa è il sistema esadecimale o sistema posizionale in base 16 dove abbiamo a disposizione le seguenti cifre 0,1,2,3,4,5,67,8,9,A,B,C,D,E,F in tutto 16 cifre (da 0 a 15 se trasformato in decimale). Questo sistema risulta comodo per leggere e scrivere numeri binari in maniera molto più compatta e leggibile. Per esempio il numero binario "10001010" (o "10001010b" dove b sta per notazione bianria) verrebbe scritto come "8A" (o "8Ah" dove h sta per notazione esadecimale). Notiamo subito che ogni cifra esadecimale rappresenta 4 cifre binarie o bit (4 bit - chiamato anche nibble o mezzo byte). Più precisamente la cifra esadecimale "A" corrisponde alla cifra binaria "1010", mentre "8" corrisponde a "1010".
Abbiamo appena accennato i diversi termini informatici che rappresenteranno le diverse grandezze che diventeranno il nostro pane quotidiano se vogliamo essere programmatori di Assembly o di qualsiasi altro linguaggio di programmazione:
bit - e il dato elementare del computer e può assumere valore zero (0) oppure uno (1) (2 numeri)
nibble - è composto da 4 bit (mezzo byte) e può assumere valori binari che vanno da 0000b a 1111b (da 0 a 15 in decimale - 16 numeri)
byte - è composto da 8 bit e può assumere valori binari che vanno da 00000000b a 11111111b (da 0 a 255 in decimale - 256 numeri)
parola o word - è composto da 16 bit (2 byte) e può assumere valori binari che vanno da 00000000 00000000b a 11111111 11111111b (da 0 a 65.535 in decimale - 65.536 numeri)
doppia parola o double word è composto da 32 bit (4 byte) e può assumere valori binari che vanno da 00000000 00000000 00000000 00000000b a 11111111 11111111 11111111 11111111b (da 0 a 4.294.967.295 in decimale - 4.294.967.296 numeri)
Ho voluto riportare anche gli intervalli numerici corrispondenti perché più avanti questi assumeranno un'importanza particolare sull'indirizzamento della memoria e principalmente sulla quantità di memoria gestibile dal computer.
CONVERSIONI NUMERICHE DA UN SISTEMA AD UN ALTRO
Vediamo ora, attraverso degli esempi, come passare da un sistema numerico all'altro.
Conversione da sistema binario a sistema decimale
Per convertire tutti i sistemi numerici in sistema decimale utilizzeremo il valore posizionale.
Per convertire il valore 10011b, prendiamo ogni cifra partendo da destra verso sinistra e la moltiplichiamo per la base del sistema numerico di origine (binario in questo caso) elevata a potenza in base alla posizione della cifra stessa: (1*2^0)+(1*2^1)+(0*2^2)+(0*2^3)+(1*2^4) = 1 + 2 + 0 + 0 + 16 = 19 decimale o 19d.
Conversione da sistema decimale a sistema binario
Per convertire il valore decimale 19 basterà dividere per la base del sistema numerico di destinazione (2 nel caso del sistema binario) e considerare il resto di ogni divisione come cifra che compone il numero binario, partendo dalla cifra meno significativa. Il risultato di ogni divisione dovrà a sua volta essere diviso nuovamente per la base 2 fino a che lo stesso risultato non assuma un valore pari a 0.
Quindi 19 / 2 = 9 con il resto di 1, 9 / 2 = 4 con il resto di 1, 4 / 2 = 2 con il resto di 0, 2 / 2 = 1 con il resto di 0, 1 / 2 = 0 con il resto di 1. Avendo quindi avuto i seguenti resti 1, 1, 0, 0 e 1. Invertendo l'ordine delle cifre avremo il nostro valore binario 10011b.
Conversione da sistema esadecimale a sistema decimale
Per convertire un numero esadecimale come AF1C (o AF1Ch) in sistema decimale basterà trasformare ogni singola cifra/lettera in sistema decimale e moltiplicarla per il valore della base esadecimale (16) elevata a potenza in base alla posizione della cifra stessa, la stessa procedura utilizzata per convertire da sistema binario a sistema decimale.
Iniziamo quindi da A che nel sistema decimale equivale a 10, scriveremo: AF1Ch = (10*16^3)+(15*16^2)+(1*16^1)+(12*16^0) = 10*4096 + 15*256 + 1*16 + 12 = 44828d
Conversione da sistema decimale a sistema esadecimale
Anche per questa conversione, come per la conversione da sistema decimale a sistema binario, divideremo per la base 16 (esadecimale) e prenderemo in condiderazione il resto di ogni divisione. Quindi 44828 / 16 = 2801 con il resto di 12 (C in esadecimale), 2801 / 16 = 175 con il resto di 1 (1 in esadecimale), 175 / 16 = 10 con il resto di 15 (F in esadecimale), 10 / 16 = 0 con il resto di 10 (A in esadecimale). Avendo quindi avuto i seguenti resti C, 1, F e A. Invertendo l'ordine delle cifre avremo il nostro valore esadecimale AF1Ch.
Di seguito riportiamo una tabella di corrispondenze delle prime 16 cifre (da 0 a 15) dei vari sistemi numerici:
Binario Decimale Esadecimale
0000 0 0
0001 1 1
0010 2 2
0011 3 3
0100 4 4
0101 5 5
0110 6 6
0111 7 7
1000 8 8
1001 9 9
1010 10 A
1011 11 B
1100 12 C
1101 13 D
1110 14 E
1111 15 F
COMPLEMENTO A DUE
Solitamente, in informatica, la rappresentazione dei numeri con il segno (+ o -) viene eseguita con il metodo del complemento a due. Il bit più significativo (bit più a sinistra) di un valore binario di 8, 16, 32 o 64 bit, viene considerato, dal processore, come il bit che indica il segno positivo (se uguale a 0) o negativo (se uguale a 1). Se invece il nostro programma dovesse considerare sono variabili, e quindi numeri, senza segno, questo bit verrebbe considerato come facente parte del valore da rappresentare, infatti il numero rappresentabile raddoppierebbe (es. un range di valori binari che va da 00000000b a 11111111b potrebbe rappresentare valori decimali con segno che vanno da -127d a +127d o valori decimali senza segno, quindi solo positivi, che vanno da 0d a 255d)
I numeri negativi sono conosciuti come il complemento a due dei numeri positivi. Questo perché per passare da un numero positivo ad uno negativo sono necessarie due fasi. La prima inverte tutti i valori binari del numero (1 diventa 0 e viceversa), la seconda aggiunge 1 alla cifra binaria ottenuta dopo la prima fase.
Se per esempio volessimo convertire il valore 5Fh, che in binario è rappresentato da 0101 1111b, dopo la prima fase avremo 1010 0000b e aggiungendo 1 il numero binario diverrà:
1010 0000+
1
---------
1010 0001
che in esadecimale corrisponde a FF5Fh, da cui 0 - FF5Fh = A1h, quindi se sommassimo FF5Fh + A1h = 0 e quindi FF5Fh = -A1h.
Per chi volesse approfondire il complemento a due consigliamo la pagina di Wikipedia: complemento a due