Manuale di programmazione in Assembler

Programmare in Assembly il Processore 80386 in Modalità Protetta con NASM

Informazioni sul documento

Lingua Italian
Formato | PDF
Dimensione 1.02 MB

Riassunto

I.Introduzione

Questo libro fornisce una comprensione approfondita di come i computer funzionano a basso livello rispetto ai linguaggi di programmazione ad alto livello come Pascal e C++.

II.Scopo del Libro

Il libro mira ad aiutare i lettori a sviluppare software in modo più produttivo nei linguaggi di alto livello.

III.Imparare l Assembly

L'apprendimento del linguaggio assembly è altamente raccomandato per comprendere il funzionamento dei computer e sviluppare software in modo efficiente.

IV.Hardware e Software

Il libro descrive il ruolo dell'orologio nella gestione dei tempi del computer e l'importanza dei compilatori per convertire i programmi in linguaggio macchina nativa.

1. Hardware e Software 1.1 Introduzione

Lo scopo di questo libro e' di fornire al lettore una migliore comprensione di come i computer in realta' lavorano ad un piu' basso livello rispetto ai linguaggi di programmazione come il Pascal. Con l’approfondimento di come funzionano i computer, il lettore puo’ essere molto piu’ produttivo nello sviluppare software nei linguaggi di alto livello come il C e C++.

1.2 Il linguaggio macchina

I linguaggi macchina sono stati definiti e con- cepiti per raggiungere questo obbiettivo e non per essere decifrati facilmente dalle persone.

1.3 Assemble e compilazione

I programmi scritti in altri linguaggi devono essere converti- ti nel linguaggio macchina nativo della CPU che si trova sul computer. Un compilatore e’ un programma che traduce i programmi scritti in linguaggi di programmazione di alto livello nel linguaggio macchina per una particolare architettura hardware.

1.4 Un primo programma

I prossimi programmi nel testo partiranno da un piccolo programma gui- da in C mostrato in Figura 1.6. Questo programma semplicemente chiama un’altra funzione, asm main. Questa e’ la routine scritta in assembly.

2. Operazioni di base 2.1 Rappresentazione dei dati

Come visto prima, L’istruzione add effettua somme mentre l’istruzione sub esegue sottrazioni. Due dei bit del registro FLAGS che queste istruzioni impostano sono i flag di overflow e di carry

2.2 Operazioni logiche

L’istruzione logiche AND (and), OR (or), XOR (xor) e NOT (not) eseguono operazioni bit a bit tra il registro sorgente e il registro o memoria di destinazione:

2.3 L aritmetica del complemento a 2

L’istruzione add effettua somme mentre l’istruzione sub esegue sottrazioni. Due dei bit del registro FLAGS che queste istruzioni impostano sono i flag di overflow e di carry. Il flag di overflow e’ impostato quando il risultato di una operazione e’ troppo grande per stare nella de- stinazione, nella aritmetica con segno. Il flag di carry e’ impostato se una addizione ha generato un riporto nel suo msb oppure se una sottrazione ha generato un resto nel suo msb.

2.4 Operatori di bit

Il proce ssore gestisce gli shift come qualsiasi altra operazione: vengono sempre effettuati su una copia del valore contenuto nel registro, la quale viene poi spostata e sovrascritta dalla copia spostata. La tabella seguente riporta i formati ammessi per le istruzioni di shift:

2.5 Operazioni di divisione

I due operatori di divisione sono DIV e IDIV.Questi eseguono la divisione tra interi senza segno e con segno rispettivamente. Il formato generale e’:

div source

2.6 Operazioni di salto e chiamata

Le istruzioni jmp (salto incondizionato) e call (chiamata a subroutine) creano dei rami nel codice del programma. Il loro formato e’: jmp dest call dest

3. Le interruzioni 3.1 Concetti di base

E’ molto comune per le routine in assembly essere interfacciate con il C. Un vantaggio di questo approccio e’ che il codice assembly puo’ utilizzare le routine della libreria I/O standard del C.

3.2 Servizi sistema operativo

Le API di molti sistemi operativi ( come POSIX e Win32) contengono funzioni che lavorano su operandi codificati come bit.

3.3 Rappresentazione dei numeri in virgola mobile

L’IEEE (Institute of Electrical and Electronic Engineers - Istituto di in- gegneria elettrica ed elettronica) e’ una organizzazione internazionale che ha creato degli specifici formati binari per la memorizzazione dei numeri in virgola mobile.

3.4 Il coprocessore numerico

I primi processori Intel non avevano supporto hardware per le operazi- ni in virgola mobile. Cio’ significa che dovevano eseguire delle procedure composte da molte istruzioni non in virgola mobile. Per questi primi siste- mi, l’Intel forni un chip addizionale chiamato coprocessore matematico.

3.5 Inizializzazione di subroutine e utilizzo della memoria

Un sottoprogramma e’ una unita’ indipendente di codice che puo’ essere usata da parti diverse del programma. In altre parole, un sottoprogramma equivale ad una funzione in C.

4. Convenzioni di chiamata

Quando viene invocato un sottoprogramma, il codice chiamante ed il sottoprogramma il chiamato devono avere accordo su come vengono passati i dati fra loro.

5. Code assembly inline

Le macro sono usate perche’ eliminano il sovraccarico dell’esecuzione di una chiamata di funzione, per le funzioni piu’ semplici.

6. Programmazione orientata agli oggetti

In C++, le funzioni membro sono diverse rispetto alle altre funzioni. A loro viene passato un parametro nascosto. Questo parametro e’ un puntatore all’oggetto su cui le funzioni membro agiscono.

7. Polimorfismo

Questa discussione si focalizza su come funzionano gli operatori di ad- dizione e sottrazione dal momento che e’ qui che viene usato il linguggio assembly. La Figura 7.17 mostra la parte di interesse dei file di header per questi operatori.

V.Interruzioni e Routine

Il libro illustra i tipi di interruzioni e le loro fonti, nonché l'uso di routine in assembly per interfacciarsi con il C.

1. Interruzioni

Le interruzioni sono generate da sorgenti esterne od interne alla CPU. Le interruzioni esterne sono generati da periferiche di I/O, mentre le interruzioni interne sono generate da errori o istruzioni di interrupt. Le interruzioni di errore sono chiamate traps, mentre le interruzioni generate da istruzioni sono chiamate interrupt software.

2. Routine

L'autore ha sviluppato le proprie routine per semplificare l'I/O, che nascondono le complesse regole del C e forniscono un'interfaccia più semplice. La Tabella 1.4 descrive queste routine.

VI.Assembly e C

Il libro fornisce esempi di codice per dimostrare l'integrazione di assembly e C, evidenziando i vantaggi dell'interfacciamento e le regole per passare informazioni.

VII.Conversione Numerica

Il libro spiega la conversione dei numeri decimali in esadecimali e la loro rappresentazione in computer.

VIII.Aritmetica e Registri in Assembly

Il libro descrive le istruzioni di addizione, sottrazione e divisione, nonché il ruolo dei registri nell'archiviazione dei dati in assembly.

1.2.3 Arithmetic e Registri

L’istruzione add effettua somme mentre l’istruzione sub esegue sottrazioni. Due dei bit del registro FLAGS che queste istruzioni impostano sono i flag di overflow e di carry. Il flag di overflow e’ impostato quando il risultato di una operazione e’ troppo grande per stare nella de- stinazione, nella aritmetica con segno. Il flag di carry e’ impostato se una addizione ha generato un riporto nel suo msb oppure se una sottrazione ha generato un resto nel suo msb. Cio’ puo’ essere usato per determinare l’o- verflow per l’aritmetica con segno. L’uso del flag di carry per l’aritmetica con segno sara’ visto tra breve. Uno dei grossi vantaggi del complemento a due e’ che le regole per l’addizione e la sottrazione sono esattamente le stesse come per gli interi senza segno. Ne deriva che add e sub possono essere usati per gli interi con e senza segno.I due operatori di divisione sono DIV e IDIV.Questi eseguono la divisione tra interi senza segno e con segno rispettivamente. Il formato generale e’: div source Se l’operando source e’ a 8 bit, allora AX e’ diviso per l’operando. Il quo- ziente e’ memorizzato in AL e il resto in AH. Se source e’ a 16 bit, allora DX:AX e’ diviso per l’operatore. Il quoziente e’ memorizzato in AX e il resto in DX. Se source e’ a 32 bit, infine, EDX:EAX e’ diviso per l’operando e il quoziente e’ memorizzato in EAX e il resto in EDX. L’istruzione IDIV funziona nello stesso modo. Non ci sono istruzioni speciali IDIV come quella IMUL. Se il quoziente e’ troppo grande per stare nel suo registro o se il divi- sore e’ 0, il programma e’ interrotto e terminato. Un’errore molto comune e’ dimenticare di inizializzare DX o EDX prima della divisione.

IX.Salti Condizionati

Il libro spiega come controllare il flusso di esecuzione del programma utilizzando istruzioni di salto condizionato.

X.APIs di Sistema

Il libro discute l'uso di API di sistema, come POSIX e Win32, per manipolare bit e file.

1. APIs di sistema

Le API (Application Programming Interface) di molti sistemi operativi (come POSIX e Win32) contengono funzioni che operano su operandi codificati in bit. Ad esempio, il sistema POSIX mantiene le autorizzazioni sui file per tre tipi diversi di utenti: utente (un nome migliore sarebbe proprietario), gruppo e altri.

2. Vecchi e nuovi programmi

Il primo programma del capitolo 1 è stato riscritto per utilizzare un sottoprogramma. Il codice sotto mostra come ciò viene fatto utilizzando una forma indiretta della istruzione JMP. Questa forma utilizza il valore di un registro per determinare dove deve saltare (in questo modo il registro si comporta come un puntatore a funzione in C.).

3. Convenzioni di chiamata per i sottoprogrammi

Quando viene invocato un sottoprogramma, il codice chiamante e il sottoprogramma chiamato devono concordare su come vengono passati i dati tra di loro. I linguaggi di alto livello hanno dei modi standard per passare i dati conosciuti come convenzioni di chiamata.

4. Interfacciamento dell assembly con il C

Molti compilatori C appongono un singolo carattere underscore (_) all'inizio del nome delle funzioni e delle variabili globali/statiche. Per esempio, ad una funzione di nome f sarà assegnata l'etichetta f. Cosi', se si tratta di una sottoroutine in assembly, deve essere etichettata f, non f.

5. Valori di ritorno dei sottoprogrammi

Le funzioni C non-void restituiscono un valore. Le convenzioni di chiamata del C specificano come questo deve essere fatto. I valori di ritorno sono passati attraverso i registri.

6. Altre convenzioni di chiamata

Le regole sopra descrivono la convenzione di chiamata del C standard che viene supportata da tutti i compilatori C x86. Spesso i compilatori supportano anche altre convenzioni di chiamata. Quando ci si interfaccia con il linguaggio assembly è molto importante conoscere quale convenzione viene utilizzata dal compilatore quando chiama una funzione.

XI.Endianness

Il libro spiega la rappresentazione little-endian e big-endian dei numeri in memoria e il suo impatto sulla programmazione.

1. Endianness

In questo sottocapitolo dell'Introduzione, l'autore affronta il concetto di Endianness, ovvero l'ordine in cui i byte vengono memorizzati in memoria. Vengono descritti i due principali ordini di byte, little-endian e big-endian, e viene spiegato come l'ordine dei byte influisca sull'interpretazione e sull'utilizzo dei dati. L'autore sottolinea anche l'importanza di comprendere l'ordine dei byte per scrivere programmi efficienti e portabili.

XII.Contare i Bit

Il libro presenta tre metodi per contare i bit in una double word arbitraria.

2. Contare i Bit

Il metodo 2 utilizza una tabella di controllo, ma questo approccio presenta alcuni problemi. L’approccio migliore è invece il metodo 3, che utilizza un sottoprogramma per contare i bit di una doppia parola arbitraria. Questo sottoprogramma viene chiamato dalla funzione principale main, che gestisce anche l’input e l’output.

XIII.Sottoprogrammi

Il libro descrive come creare sottoprogrammi in assembly e come llamarli dal codice sorgente.

1. Sottoprogrammi

Un sottoprogramma è un'unità di codice indipendente che può essere utilizzata da diverse parti del programma. In altre parole, un sottoprogramma equivale a una funzione in C. Il sottoprogramma può essere richiamato con un'istruzione di salto, ma il suo ritorno può presentare un problema. Se il sottoprogramma viene utilizzato da diverse parti del programma, deve necessariamente tornare alla sezione di codice che lo ha invocato. Così il salto indietro dal sottoprogramma non può essere collegato ad un'etichetta.

2. Convenzioni di chiamata

Quando viene invocato un sottoprogramma, il codice chiamante e il sottoprogramma chiamato devono avere accordo su come vengono passati i dati fra loro. I linguaggi di alto livello hanno dei modi standard per passare i dati conosciuti come convenzioni di chiamata. Per interfacciare il codice di alto livello con il linguaggio assembly, il codice assembly deve usare la stessa convenzione del linguaggio di alto livello.

3. Interfacciare l Assembly con il C

I compilatori C appongono un singolo carattere underscore( ) all'inizio del nome delle funzioni e delle variabili globali/statiche. Per esempio, ad una funzione di nome f sarà assegnata l'etichetta f. Cosi', se si tratta di una sottoroutine in assembly, deve essere etichettata f, non f. Il compilatore gcc Linux non prepende nessun carattere. Negli eseguibili ELF di Linux, si usa semplicemente l'etichetta f per la funzione C f. Il gcc di DJGPP invece pre- pende un underscore. Nota che nello scheletro del programma(Figure 1.7), l'etichetta della routine principale e' asm main.

XIV.Convenzioni di Chiamata

Il libro spiega le convenzioni di chiamata utilizzate per passare parametri tra assembly e linguaggi di alto livello, come C.

1. Convenzioni di chiamata

Le convenzioni di chiamata sono accordi tra il codice chiamante e il codice chiamato su come vengono passati i dati tra di loro. Sono cruciali per interfacciare il codice di alto livello con il linguaggio assembly.

2. Convenzione di chiamata C standard

La convenzione di chiamata C standard è comunemente utilizzata e specifica come vengono passati i valori di ritorno, gli argomenti e i dati in memoria.

3. Convenzioni di chiamata alternative

Oltre alla convenzione di chiamata C standard, esistono altre convenzioni come la convenzione pascal e stdcall, utilizzate in contesti specifici come le API di Windows e le funzioni MSX.

XV.Assembly Inline

Il libro discute le tecniche di chiamata di routine di assembly e l'assembly inline per incorporare l'assembly direttamente nel codice C.

1. Assembly Inline

L’assembly inline permette al programmatore po- sizionare comandi assembly direttamente nel codice C. Questo puo’ essere molto conveniente; d’altra parte, ci sono degli svantaggi. Il codice assembly deve essere scritto nel formato che usa il compilatore. Nessun compilatore al momento supporta il formato del NASM. Compilatori differenti richiedono formati diversi. Borland e Microsoft richiedono il formato MASM. DJGPP e il gcc di Linux richiedono il formato GAS 5 .

XVI.Interfaccia Assembly C

Il libro fornisce linee guida per interfacciare assembly e C, inclusi metodi per passare array multidimensionali come parametri.

1.4.1 Il Primo Programma

I prossimi programmi nel testo partiranno da un piccolo programma gui- da in C mostrato in Figura 1.6. Questo programma semplicemente chiama un’altra funzione, asm main. Questa e’ la routine scritta in assembly. Ci sono molti vantaggi nell’utilizzare una routine guida scritta in C. Primo, permette all’archiettura del C di impostare correttamente il programma per girare in modalita’ protetta. Tutti i segmenti e i loro corrispondenti registri segmento saranno inizializati dal C. Il codice assembly non si deve occupare e preoccupare di questa operazione. Secondo, la libreria C sara’ disponibile anche da codice assembly. Le routine I/O dell’autore traggono vantaggio da cio’. Infatti utilizzano le funzioni di I/O del C (es. printf). Il codice sotto mostra un semplice programma in assembly:

52 00000023 65723A2000 La prima colonna di ogni riga e’ il numero di riga, mentre la seconda e’ l’offset (in Hex) dei dati nel segmento. La terza colonna mostra i valori esadecimali che saranno memorizzati. In questo caso i valori esadecimali corrispondono ai codice ASCII. Infine, il testo del codice sorgente corrispondente. Gli offset della seconda colonna in realt non sono i veri offset a cui saranno memorizzati i dati nel programma definitivo. Ogni modulo puo’ definire le proprie etichette nel segmento dati ( ed anche in altri segmenti). Nella fase di collegamento (vedi sezione 1.4.5), tutte queste definizioni di etichette nei segmenti dati saranno combinate per formare un unico segmento dati. Il nuovo definitivo offset sara’ a quel punto calcolato dal linker.

Valori di ritorno

Le funzioni C non-void restituiscono un valore. Le convenzioni di chia- mata del C specificano come questo deve essere fatto. I valori di ritorno sono passati attraverso i registri. Tutti i tipi integrali (char, int, enum, etc.) sono ritornati nel registro EAX. Se sono piu’ piccoli di 32 bit, vengono estesi a 2 bit nel momento in cui vengono messi in EAX. (Come sono estesi dipende se sono con segno o senza segno.) I valori a 64 bit sono ritornati nella coppia di registri EDX:EAX. Anche i valori puntatore sono memoriz- zati in EAX. I Valori in virgola mobile sono memorizzati nel registro STO del coprocessore matematico. (Questo registro sara’ discusso nel capitolo sulla virgola mobile.)

Etichette di Funzioni

Molti compilatori C appongo un singolo carattere underscore( ) all’inizio del nome delle funzioni e delle variabili globali/statiche. Per esempio, ad una funzione di nome f sara’ assegnata l’etichetta f. Cosi’, se si tratta di una sottoroutine in assembly, deve essere etichettata f, non f. Il compilatore gcc Linux non prepende nessun carattere. Negli eseguibili ELF di Linux, si usa semplicemente l’etichetta f per la funzione C f. Il gcc di DJGPP invece pre- pende un underscore. Nota che nello scheletro del programma(Figure 1.7), l’etichetta della routine principale e’ asm main.

Passaggio di Array Multidimensionali come Parametri in C

La rappresentazione a livello di riga degli array multidimensionali ha un effetto diretto nella programmazione in C. Per gli array ad una dimesio- ne, ls dimensione dell’array non e’ necessaria per il calcolo della posizione di uno specifico elemento in memoria. Questo non e’ vero per gli array multidimensionali. Per accedere agli elementi di questi array, il compila- tore deve conoscere tutte le dimensioni ad eccezione della prima. Questo diventa apparente quando consideriamo il prototipo di una funzione che ac- cetta un array multidimensionale come parametro. Il seguente prototipo non verrebbe compilato:

Rappresentazione in virgola mobile IEEE

L’IEEE (Institute of Electrical and Electronic Engineers - Istituto di in- gegneria elettrica ed elettronica) e’ una organizzazione internazionale che ha creato degli specifici formati binari per la memorizzazione dei numeri in virgola mobile. Questo formato e’ il piu’ utilizzato (ma non l’unico) dai com- puter prodotti oggigiorno. Spesso e’ supportato direttamente dall’hardware del computer stesso. Per esempio, il processore numerico (o matematico) della Intel (che viene montato su tutte le CPU a partire dal Pentium) uti- lizza questo formato. L’IEEE definisce due diversi formati con differenti precisioni: precisione singola e precisione doppia. In C, la precisione singola e’ utilizzata dalla variabili float e la precisione doppia da quelle double.

Il Coprocessore Numerico

I primi processori Intel non avevano supporto hardware per le operazi- ni in virgola mobile. Cio’ significa che dovevano eseguire delle procedure composte da molte istruzioni non in virgola mobile. Per questi primi siste- mi, l’Intel forni un chip addizionale chiamato coprocessore matematico. Un coprocessore matematico ha delle istruzioni macchina che eseguono molte operazioni in virgola mobile molto piu’ velocemente rispetto alle procedure software (sui primi processori, almeno 10 volte piu’ velocemente!). Il copro- processore per l’8086/8088 era chiamato 8087. Per l’80286 c’era l’80287 e per l’80386, l’80387. Il processore 80486DX integrava il coprocessore matemati- ca nello stesso 80486. 5 Da Pentium in poi, tutte le generazioni di processori 80x86 hanno un coprocessore matematico integrato. Viene comunque pro- grammmato come se fosse una unita’ separata. Anche sui primi sistemi senza un coprocessore si puo’ installare software che emula un coprocessore ma- tematico. Questi pacchetti di emulazione sono attivati automaticamente quando un programma esegue una istruzione del coprocessore ed eseguono una procedura software che produce lo stesso risultato che si avrebbe col il coprocessore (sebbene molto piu’ lentamente, naturalmente).

Riferimenti

I riferimenti sono un’altra nuova caratteristica del C++. Questi permet- tono il passaggio di parametri ad una funzione senza usare esplicitamente i puntatori. Per esempio, consideriamo il codice in Figura 7.11. In realta’, i parametri riferimento sono molto semplici, sono giusto dei puntatori. Il compilatore semplicemente nasconde cio’ al programmatore (come un com- pilatore Pascal implementa i parametri var come puntatori). Quando il compilatore genera l’assembly per la chiamata di funzione alla riga 7, passa l’indirizzo di y.

Funzioni in Linea

I riferimenti sono solo una convenienza che puo’ essere molto utile in particolare per il sovraccaricamento degli operatori. Questa e’ un’altra ca- ratteristica del C++ che permette di ridefinire il significato dei piu’ comuni operatori, per i tipi struttura e classe. Per esempio, un uso comune e’ quel- lo di ridefinire l’operatore piu’ (+) per concatenare oggetti stringa. Cosi’, se a e b sono stringhe, a + b dovrebbe ritornare la concatenazione delle stringhe a e b. Il C++ in realta’ chiamera’ una funzione per fare cio’ (infat- ti, queste espressioni possono essere riscritte in notazione di funzione come operator +(a,b)). Per efficenza, occorre passare l’indirizzo dell’oggetto stringa invece del loro valore. Senza riferimenti, questo potrebbe essere fat- to come operator +(&a,&b), ma questo richiederebbe di scriverlo con la sintassi dell’operatore, cioe’ &a + &b. Cio’ potrebbe essere molto problema- tico. Invece, con l’utilizzo dei riferimenti, si puo’ scriverlo come a + b, che appare molto naturale.

XVII.Virgola Mobile

Il libro introduce la rappresentazione in virgola mobile IEEE e l'uso del coprocessore numerico per le operazioni in virgola mobile.

1. Virgola Mobile

  • L'autore ha sviluppato le proprie routine per semplificare l'I/O, nascondendo le complesse regole del C e fornendo un'interfaccia più semplice.
  • La tabella 1.4 descrive queste routine.
  • Tutte le routine preservano il valore di tutti i registri ad eccezione delle routine di lettura.
  • Queste routine modificano il valore del registro EAX.

2. Virgola Mobile

  • I due operatori di divisione sono DIV e IDIV.
  • Questi eseguono la divisione tra interi senza segno e con segno rispettivamente.
  • Il formato generale è: div source
  • Se l'operando source è a 8 bit, allora AX è diviso per l'operando.
  • Il quoziente è memorizzato in AL e il resto in AH.
  • Se source è a 16 bit, allora DX:AX è diviso per l'operatore.
  • Il quoziente è memorizzato in AX e il resto in DX.
  • Se source è a 32 bit, infine, EDX:EAX è diviso per l'operando e il quoziente è memorizzato in EAX e il resto in EDX.
  • L'istruzione IDIV funziona nello stesso modo.

XVIII.Strutture

Il libro spiega la creazione di strutture, la gestione degli offset e l'interfacciamento tra strutture C e assembly.

Strutture

Strutture sono raccolte correlate di dati memorizzati insieme. Usando le Strutture, è possibile raggruppare i dati in un modo più logico e strutturato.

XIX.Packing dei Bit

Il libro discute la gestione dei campi di bit, le differenze tra i compilatori e l'utilizzo del macro offsetof() per calcolare gli offset degli elementi.

1. Packing dei Bit

Il codice a 32 bit memorizza gli interi in formato little endian, mentre il codice a 16 bit in formato big endian. Il formato little endian rappresenta il byte meno significativo nella posizione di memoria più bassa, mentre il formato big endian fa il contrario. Questa differenza può essere confusa per i programmatori che lavorano su piattaforme diverse e può comportare la necessità di convertire i dati tra i due formati.

2. Metodo 1 Precalcolo

Questo metodo precalcola il numero di bit in ogni double word possibile e memorizza questi dati in un array. Tuttavia, questo può richiedere molto tempo ed essere poco pratico per grandi array.

3. Metodo 2 Tabella di controllo

Una tabella di controllo può essere utilizzata per contare i bit di una double word arbitraria. Questa tabella memorizza il numero di bit in ogni possibile valore a 8 bit e viene utilizzata per determinare il numero di bit nella double word.

4. Metodo 3 Sottoprogramma

Un sottoprogramma è una funzione indipendente che può essere utilizzata da varie parti di un programma. Può essere utilizzato per contare i bit in una double word arbitraria e restituire il conteggio.

XX.Overloading e C

Il libro spiega l'overloading in C++ e come i nomi delle funzioni vengono modificati per evitare conflitti.

1. Overloading di C

Il codice non modificato può calcolare l'opcode per l'istruzione mov (che dall'elenco è A1), ma scrive l'offset tra parentesi quadre perché il valore esatto non può ancora essere calcolato. In questo caso viene utilizzato un offset temporaneo di 0 poiché input1 è all'inizio del segmento bss definito nel file. Ricorda che ciò non significa che input1 sarà all'inizio del segmento bss definitivo del programma. Quando il codice è collegato, il linker inserirà il valore corretto dell'offset nella posizione.

2. Overloading di emC

Il valore di ritorno della funzione non è parte della firma della funzione e non è codificato nel suo nome modificato. Questo spiega una regola del sovraccarico in C++. Solo le funzioni, la cui firma è unica, possono essere sovraccaricate. Come si può vedere, se vengono definite in C++ due funzioni con lo stesso nome e con la stessa firma, queste produrranno lo stesso nome modificato e genereranno un errore nel collegamento.

3. Funzioni membro overloading in emC

La chiamata ad una funzione non in linea richiedere solo di conoscere i parametri, il tipo del valore di ritorno, la convenzione di chiamata e il nome della etichetta per la funzione. Tutte queste informazioni sono disponibili dal prototipo della funzione. Utilizzando una funzione in linea, è necessario conoscere il codice di tutta la funzione. Questo significa che se una parte della funzione è modificata, tutti i file sorgenti che usano quella funzione devono essere ricompilati. Ricordati che per le funzione non in linea, se il prototipo non cambia, spesso i file che usano la funzione non necessitano di essere ricompilati.

4. Polimorfismo e Overloading in emC

Cosa c’è a offset 0? La risposta a questa domanda è collegata a come viene implementato il polimorfismo. Una classe C++ che ha tutti i metodi virtuali, viene dotata di campo aggiuntivo, nascosto, che rappresenta un puntatore ad un’array di punta- tori ai metodi 14 . Questa tabella e’ spesso chiamata vtable. Per le classi A e B questo puntatore è memorizzato ad offset 0. I compilatori Windows mettono sempre questo puntatore all’inizio della classe all’apice dell’albero delle ereditarieta’. Guardando al codice assembly (Figura 7.22) generato per la funzione f (dalla Figura 7.19) per la versione con metodo virtuale del programma , si puo’ vedere che la chiamata al metodo m non e’ ad una etichetta. La riga 9 trova l’indirizzo dell’oggetto nella vtable. L’indirizzo dell’oggetto e’ messo nello stack alla riga 11. La riga 12 chiama il metodo virtuale saltando al primo indirizzo della vtable 15 . Questa chiamata non usa

5. Variabili membro overloading di emC

Ci sono alcune lezioni pratiche da imparare da questo esempio. Un fatto importante e’ che occorre stare molto attenti quando si leggono e scrivono variabile classe da un file binario. Non e’ possibile utilizzare una lettura o scrittura binaria dell’intero oggetto dal momento che queste operazioni leg- gerebbero o scriverebbero il puntatore alla vtable dal file! Questo puntatore indica dove risiede la vtable nella memoria del programma e puo’ variare da programma a programma. Lo stesso problema nasce in C con le struct, ma in C le struct hanno dei puntatori solo se il programmatore le mette esplicitamente nelle struct. Non ci sono puntatori ovvii definiti nella classe A ne’ nella classe B.'

.Riferimenti

Il libro introduce i riferimenti in C++, che consentono il passaggio di parametri senza puntatori espliciti.

Riferimenti

In questo capitolo viene presentato il programma di assemblaggio NASM. Per maggiori dettagli, visitate il sito ufficiale del progetto: https://www.nasm.us/

.Assembly e C

Il libro discute l'interfacciamento tra assembly e C++, inclusi i riferimenti, le funzioni in linea e le macro.

.Classi e Metodi Virtuali

Il libro spiega la gestione della memoria per classi C++ con metodi virtuali e l'uso di vtable per l'implementazione del polimorfismo.

1. Classi e Metodi Virtuali in C

Il linguaggio di programmazione C++ introduce il concetto di classi e metodi virtuali per implementare il polimorfismo, che consente agli oggetti di comportarsi in modo diverso in base al tipo di classe a cui appartengono. In C++, le classi con metodi virtuali hanno un campo aggiuntivo nascosto, chiamato vtable, che contiene un array di puntatori ai metodi della classe. Quando viene chiamato un metodo virtuale, il compilatore utilizza la vtable per determinare l'indirizzo del metodo corretto da eseguire, in base al tipo dell'oggetto su cui è stato chiamato il metodo.

2. Formato Vtable

In Windows, i compilatori Microsoft e Borland memorizzano sempre il puntatore alla vtable all'inizio della classe, all'apice dell'albero di ereditarietà. La vtable contiene puntatori ai metodi della classe, con il primo puntatore che punta al metodo con offset 0 nella vtable. Gli oggetti ereditati condividono la stessa vtable della classe genitore se ereditano i metodi da essa.

3. Chiamate a Metodi Virtuali

Quando viene chiamato un metodo virtuale, il compilatore utilizza la vtable per determinare l'indirizzo del metodo corretto da eseguire. Questo viene fatto trovando l'indirizzo della vtable per l'oggetto, che è memorizzato all'offset 0, e quindi utilizzando il puntatore alla vtable per l'offset del metodo desiderato. Questo consente alle chiamate a metodi virtuali di essere risolte dinamicamente in fase di esecuzione, in base al tipo dell'oggetto su cui è stato chiamato il metodo.

4. Osservazioni sull accesso alle Variabili di Classe

Quando si leggono o scrivono variabili di classe da un file binario, bisogna evitare di utilizzare operazioni di lettura o scrittura binaria per l'intero oggetto, poiché queste operazioni leggerebbero o scriverebbero il puntatore alla vtable dal file. Questo puntatore indica dove risiede la vtable nella memoria del programma e può variare da programma a programma.

.Note di Chiusura

Il libro conclude con alcune lezioni pratiche sulla lettura e scrittura di variabili di classe dai file binari.