[1] Random Access Memory, cioè memoria ad accesso casuale, perché il contenuto di ogni sua parte può essere letto o modificato, anche più volte, in qualunque momento.
[2] Non è una verità universale: alcuni processori implementano il byte con 7 bit.
[3] I numeri in virgola mobile sono gestiti in formato esponenziale: una parte dei bit sono dedicati alla mantissa, una parte all'esponente ed uno al segno.
[4] Corrisponde alla dimensione dei registri del coprocessore matematico.
[5] Ma perché proprio tale suddivisione? Perché gli indirizzi superiori al limite dei 640Kb sono stati riservati, proprio in sede di progettazione del PC, al firmware BIOS e al BIOS delle schede adattatrici (video, rete, etc.), sino al limite di 1 Mb.
[6] In effetti, 220 = 1.048.576: provare per credere. Già che ci siamo, vale la pena di dire che le macchine basate su chip 80286 o superiore possono effettuare indirizzamenti a21 bit.
[7] Per indirizzo lineare si intende un offset relativo all'inizio della memoria, cioè relativo al primo byte della RAM.
[8] Di aritmetica dei puntatori si parla in un paragrafo dedicato.
[9] Presupposto fondamentale è l'assegnazione a numPtr dell'indirizzo di numero, come da esempio.
[10] Attenzione, però, all'istruzione
float *numPtr = №
Può infatti sembrare in contrasto con quanto affermato sin qui l'assegnazione di un un indirizzo (&numero) ad una indirezione (*numPtr) che non rappresenta un indirizzo, ma un float. In realtà va osservato che l'istruzione riportata assegna a numPtr un valore contestualmente alla dichiarazione di questo: l'operatore di indirezione, qui, serve unicamente a indicare che numPtr è un puntatore; esso deve cioè essere considerato parte della sintassi necessaria alla dichiarazione del puntatore e non come strumento per accedere a ciò che il puntatore stesso indirizza. Alla luce di tale considerazione l'assegnazione appare perfettamente logica.
[11] Ciò vale se si lascia che il compilatore lavori basandosi sui propri default. Torneremo sull'argomento in tema di modelli di memoria.
[12] I processori Intel memorizzano i valori in RAM con la tecnica backwords, cioè a "parole rovesciate". Ciò significa che i byte più significativi di ogni valore sono memorizzati ad indirizzi di memoria superiori: ad esempio il primo byte di una word (quello composto dai primi 8 bit) è memorizzato nella locazione di memoria successiva a quella in cui si trova il secondo byte (bit 815), che contiene la parte più importante (significativa) del valore.
[13] Si dicono near i puntatori non far e non huge; quelli, in altre parole, che esprimono semplicemente un offset rispetto ad un registro di segmento della CPU.
[14] Nella numerazione esadecimale, cioè in base 16, si calcola un riporto ogni 16 unità, e non ogni 10 come invece avviene nella numerazione in base decimale.
[15] Ad esempio con la dichiarazione di una variabile long double o con una chiamata ad una delle funzioni di libreria dedicate all'allocazione dinamica della memoria. Non siate impazienti...
[16] Detto tra noi, esiste un metodo più comodo per conoscere la lunghezza di una stringa: la funzione di libreria strlen(), che accetta quale parametro l'indirizzo di una stringa e restituisce, come intero, la lunghezza della medesima (escluso, dunque, il null terminator).
[17] Insomma, il programmatore dovrebbe sempre ricordare la regola KISS; il compilatore, da parte sua, applica con tenacia la regola MYOB (Mind Your Own Business: "fatti gli affari tuoi").
[18] Si possono assegnare valori solo ai suoi elementi.
[19] Il perché sarà chiarito in tema di accessibilità e durata delle variabili.
[20] Che esprime, in definitiva, la loro posizione diminuita di uno.
[21] Inoltre alcuni compilatori consentono di gestire char di tipo multibyte, che occupano una word.
[22] Va osservato che un array di puntatori a carattere potrebbe essere anche dichiarato così:
char *stringhe[10];
La differenza consiste principalmente nella necessità di indicare al momento della dichiarazione il numero di puntatori a char contenuti nell'array stesso, e nella possibilità di inizializzare l'array:
char *stringhe[] = {
"prima stringa",
"seconda stringa",
NULL,
"quarta ed ultima stringa",
};
L'array stringhe comprende 4 stringhe di caratteri, o meglio 4 puntatori a char: proprio da questo deriva la possibilità di utilizzare NULL come elemento dell'array (NULL, lo ripetiamo, è una costante manifesta definita in STDIO.H, e vale uno zero binario). In pratica, il terzo elemento dell'array è un puntatore che non punta ad aluna stringa.
[23] Il compilatore, però, si degna di emettere un apposito warning.
[24] Nella stringa di formato passata a printf(), il 2 che compare tra l'indicatore di carattere di formato ("%") e il carattere di formato stesso ("d") serve quale specificatore dell'ampiezza di campo. In altre parole esso indica che il valore contenuto nella variabile i deve essere visualizzato in uno spazio ampio 2 caratteri, assicurando così il corretto incolonnamento dei numeri visualizzati.
[25] Le macchine 8086, 8088 e 80286 dispongono esclusivamente di registri a 16 bit. Una dichiarazione come la seguente:
register long rLong;
non potrebbe che originare, su tali macchine, una normale variabile automatic, perché il tipo long integer occupa 32 bit. Su macchine 80386 (e superiori), invece, il compilatore potrebbe gestire rLong in un registro, dal momento che detti elaboratori dispongono di registri a 32 bit (a seconda del compilatore, però, potrebbe essere necessario specificare esplicitamente in fase di compilazione che si desidera generare codice eseguibile specifico per macchine 80386).
[26] In effetti, il numero dei registri macchina è limitato. E' quindi opportuno identificare le variabili più utilizzate e dichiararle per prime come register: il compilatore alloca le variabili nell'ordine in cui sono dichiarate.
[27] Suddividere il codice di un programma molto "corposo" in più file sorgenti può facilitarne la manutenzione, ma soprattutto consente, in caso di modifche, di ricompilare solo le parti effettivamente modificate, a tutto vantaggio dell'efficienza del processo di programmazione.
[28] E' preferibile utilizzare la L maiuscola, poiché, nella lettura dei listati, quella minuscola può facilmente essere scambiata con la cifra 1.
[29] Nel senso che tutti i suoi 8 bit sono impostati a zero; non va confuso col carattere '0'.
[30] Le espressioni di controllo generate da un carattere preceduto dalla backslash sono anche dette sequenze ANSI.
[32] Degli operatori C parleremo diffusamente. Il significato di ? : può essere, per il momento, dedotto dall'esempio. Per ora merita attenzione il fatto che molti compilatori implementano max() e min() proprio come macro, definite in uno dei file .H di libreria.
[33] Anche l'operatore, ++, verrà descritto ampiamente.
[34] La fase, cioè, di ricerca e correzione degli errori di programmazione. Questa è effettuata con l'aiuto di sofisticati programmi, detti debuggers, che sono spesso in grado di visualizzare il contenuto delle variabili associandovi il nome simbolico; cosa peraltro impossibile con le costanti manifeste, che ne sono prive.
[35] In tal senso strumenti molto potenti sono offerti dal C++, con il quale si possono "inventare" nuovi tipi di dato, definendone anche le modalità di manipolazione, e gestirli, se ben progettati, senza alcuna differenza rispetto a quelli elementari intrinseci.
[36] Le variabili comprese in una struttura si dicono campi.
[37] Nella pratica comune ci si riferisce di solito alla "struttura concorso" allo stesso modo che alla "struttura c0", anche se nel primo caso si intende "il modello della struttura il cui identificatore è concorso" e nel secondo "la variabile di nome c0, il cui tipo è la struct avente modello concorso". I distratti sono avvertiti.
[38] Per completezza va osservato che dichiarando la variabile struttura e contemporaneamente definendone il template la creazione di un tag può essere omessa:
struct {
int serie;
char organizzatore;
int parteciapnti;
} c0, c1;
In questo caso, non esistendo un tag mediante il quale fare riferimento al template, è necessario riscrivere il template ogni volta che si dichiara altrove una variabile avente quelle stesse caratteristiche. Da evitare, assolutamente.
[39] Un po' di ottimismo non guasta...
[40] Forse anche perché fino a qualche anno fa erano pochi i compilatori in grado di implementare tale sintassi.
[41] La dimensione di una struttura può essere ricavata mediante l'operatore sizeof(), passandogli quale argomento il tag preceduto dalla parola chiave struct, oppure il nome di una variabile struttura: basandoci sugli esempi visti sin qui, sizeof(struct concorso) e sizeof(c0) restituiscono entrambe la dimensione della struttura concorso (che nel nostro caso è pari a 5 byte).
[42] Un prevTL e un nextTL contenenti NULL segnalano che le righe gestite dalle strutture di cui fanno parte sono, rispettivamente, la prima e l'ultima del testo. Se una struct TextLine presenta entrambi i puntatori NULL, allora essa gestisce l'unica riga di testo. Se è NULL anche il puntatore alla riga, line, allora il testo è vuoto.
[44] Perché prima l'offset e poi il segmento?
E' sempre la solita storia: i processori Intel memorizzano i
byte più significativi di una variabile nelle locazioni
di memoria aventi indirizzo maggiore (tecnica backwords).
Perciò di un dato a 32 bit è memorizzato prima
il quarto byte, poi il terzo (ed è la seconda word), poi
il secondo, infine il primo (ed è la prima word).