[1] Tant'è che ogni microprocessore ha il proprio linguaggio assembler. I personal computer generalmente indicati come "IBM o compatibili" sono basati sulla famiglia di processori Intel 80x86: 8086, 80286, 80386, 80386SX, 80486 ed altri ancora. Esiste un vasto insieme di istruzioni comuni a tutti questi microprocessori: di qui la possibilità concreta di scrivere programmi in grado di girare su macchine di tipo differente. Il programmatore che intenda sfruttare le prestazioni più avanzate offerte da ciascuno di essi (in particolare dal tipo 80286 in poi) deve però rinunciare alla compatibilità del proprio programma con i processori che non dispongono di tali caratteristiche.

[2] Data la banalità della funzione in sé medesima, si tratta, in fondo, di un caso limite.

[3] Il compilatore C Borland accetta, allo scopo, l'opzione ­S:

bcc -S stampa.c

[4] Ad esempio le più recenti versioni dei compilatori Microsoft e Borland. Il contenuto del presente capitolo fa riferimento specifico alle possibilità offerte dal secondo.

[5] Per la verità, le istruzioni inline assembly vengono modificate laddove contengano riferimenti a simboli C (ad esempio nomi di variabili, come vedremo). Nella maggior parte dei casi ciò rappresenta un vantaggio, perché consente di referenziare le variabili definite direttamente in C, come nell'istruzione

    asm mov dl,p_escl;

ma in alcuni casi è fonte di grattacapi non da poco. Esempietto chiarificatore, tra i diversi possibili: se una istruzione assembly contiene l'operatore DUP, che serve a inizializzare più byte ad un dato valore, e prima di tale istruzione è incluso il file IO.H, contenente il prototipo della funzione C dup(), che duplica lo handle di un file, il compilatore la scambia (orrore!) per una chiamata a detta funzione C, confondendo le idee all'assemblatore. La soluzione è una sola: includere il file IO.H dopo tutte le righe assembly che fanno uso di DUP. Nel vostro programma non è possibile? Peggio per voi: non vi rimane che copiare il file IO.H e modificare la copia eliminando la dichiarazione del prototipo di dup(); è ovvio che nel sorgente C deve essere inclusa la copia così modificata.

[6] In alternativa, la direttiva asm può introdurre un blocco di istruzioni racchiuso, come di consueto, tra parentesi graffe:

    asm {
        mov ah,2;
        ....
        int 21h;
    }

[7] Il termine va inteso in senso lato: in questo caso con "istruzione" si indicano anche le chiamate a funzione. Le funzioni, a loro volta, possono essere considerate sequenze di istruzioni delle quali la routine chiamante conosce esclusivamente le modalità di interfacciamento al programma (in altre parole la struttura dell'input e dell'output).

[8] Essi sono cioè in grado, su richiesta, di compilare il codice sorgente in modo che il codice oggetto prodotto sia il più compatto possibile, oppure il più veloce possibile, e così via.

[9] Compilato con l'opzione ­S sulla riga di comando. Il codice riportato è solo una parte di quello prodotto dal compilatore: sono state eliminate tutte le parti relative alla segmentazione.

[10] Non bisogna dimenticare che il C garantisce che i parametri siano passati alle funzioni per valore: in altre parole alla funzione è passata una copia di ogni parametro, ottenuta copiando il valore di questo nello stack, dal quale la funzione lo preleva. Ciò implica che una funzione non può modificare il valore delle variabili che le sono date quali parametri attuali.

[11] Se la chiamata è near, cioè se il codice della funzione è compreso nello stesso segmento in cui appare la CALL, viene salvato sullo stack solamente un offset (il valore che IP deve assumere perché sia eseguita la prima istruzione che segue la CALL nella routine chiamante) e quindi SP è decrementato di 2. Se, al contrario, la chiamata è di tipo far, vengono salvati sullo stack un segmento ed un offset, e pertanto SP è decrementato di 4.

[12] Per essere più precisi: rispetto al valore che SP ha al momento dell'ingresso nella funzione, cioè dopo la CALL. Per questo entra in gioco il registro BP.

[13] In termini di word. Esempi: un int richiede una word; un long ne richiede due; tre char ne richiedono tre; un array di nove char ne richiede cinque; due array di nove char ne richiedono cinque ciascuno. E' ovvio che in assenza di variabili locali non vi sono istruzioni SUB SP,... o DEC SP in testa, né la MOV SP,BP in coda alla funzione.

[14] Tanto per fare un esempio: il compilatore Borland TURBO C 2.0 non mantiene la standard stack frame, e quindi genera le istruzioni per la gestione dello stack solo nelle funzioni in cui sono indispensabili. Il compilatore Borland C++, dalla versione 1.0 in poi, genera per default, in qualsiasi funzione, le istruzioni necessarie alla standard stack frame, se sulla command line non è specificata l'opzione ­k­. Si tornerà sull'argomento circa la gestione dei dati nel Code Segment.

[15] La possibilità di dichiarare interrupt una funzione è offerta da molti compilatori C in commercio, soprattutto nelle loro versioni recenti.

[16] Per la verità, le funzioni interrupt ritornano mediante l'istruzione IRET. La RET può essere utilizzata solo nella forma RET 2, per eliminare la copia dei flag spinta sullo stack dalla chiamata ad interrupt.

[17] D'altra parte la "A" di AX sta per "Accumulatore". Esso è anche usato in modo implicito da alcune istruzioni, quali LODSB, LODSW, STOSB, STOSW.

[18] Ad esempio, BX (Base) può essere usato nel calcolo degli offset per referenziare i membri delle strutture; CX (Counter) agisce come contatore con l'istruzione LOOP e con quelle precedute da REP; DX (Data) è destinato a generiche funzionalità di archivio dati.

[19] Source Index e Destination Index. Utilizzati, tra l'altro, come indici ad incremento o decremento automatico dalle istruzioni MOVSB, MOVSW, etc..

[20] Coloro che sono privi di immaginazione sappiano che si tratta di problemi legati alla gestione dello stack, del tutto analoghi a quelli discussi con riferimento alla funzione prova(). Nella copia() non compaiono le istruzioni DEC SP e MOV SP,BP in quanto essa non fa uso di variabili locali.

[21] Si ricordi che lo stack è sempre movimentato con un algoritmo del tipo LIFO (Last In First Out): l'ultima word entrata con una PUSH è la prima ad esserne estratta con una POP.

[22] Data Segment: usato normalmente come puntatore al segmento dati di default del C.

[23] Extra Segment: il suo utilizzo è libero.

[24] In C un evidente esempio è rappresentato dai nomi degli array.

[25] A dire il vero, i byte potrebbero essere quattro se il codice venisse compilato in modalità 80386 a 32 bit, ma ciò sarebbe, in questo caso, ininfluente.

[26] L'istruzione CBW (Convert Byte to Word) funziona solo con AX: la parte alta degli altri registri deve essere azzerata esplicitamente (ad es.: XOR BH,BH o MOV BH,0).

[27] Eccetto il caso in cui il codice sia in grado di sfruttare le caratteristiche dell'assembler 80386: in tal caso sono disponibili i registri estesi (EAX, EBX, etc.) a 32 bit.

[28] Per la precisione, se ne può usare uno solo:

    asm mov ax,ds:[si];
    asm mov var32bits,ax;
    asm mov ax,ds:[si+2];
    asm mov var32bits+2,ax;

[29] Per i dettagli sui modelli di memoria vedere il capitolo dedicato.

[30] Il compilatore ha generato una sequenza valida per ogni bit dei flag (non esiste una specifica istruzione assembler per ciascuno di essi).

[31] E' un cold bootstrap se prima di invocare la boot() il programma non scrive il numero 1234h all'indirizzo RAM 0:0472.

[32] Infatti esso punta al code segment del gestore di interrupt; in altre parole esso è la parte segmento del vettore, cioè dell'indirizzo, della routine.

[33] Meglio non fidarsi dell'opportunità, offerta da alcuni compilatori, di definire nel sorgente variabili indirizzate relativamente a CS: a differenza del metodo descritto in queste pagine, l'opzione citata non consente di controllare dove, all'interno del code segment, tali variabili saranno allocate.

[34] In realtà le funzioni potrebbero essere più di una, e ciascuna potrebbe contenere uno o più dati. Il programmatore è naturalmente libero di scegliere l'approccio che gli è più congeniale.

[35] Come si è anticipato, quello descritto è un trucco per memorizzare dati in locazioni relative a CS e non a DS, contrariamente al default del compilatore.

[36] Sembra incredibile, ma funziona.

[37] Niente paura: simili follie sono analizzate in profondità con riferimento alla gestione degli interrupt ed ai TSR. Gli esempi qui riportati vanno esaminati semplicemente dal punto di vista della sintassi.

[38] Oltre a DB (Define Byte) sono ammesse anche DW (Define Word) e DD (Define Doubleword), nonché l'estensione DUP, che va gestita con estrema attenzione: programmatore avvisato...