Copy Link
Add to Bookmark
Report
BFi numero 08 anno 3 file 24 di 28
==============================================================================
------------[ BFi numero 8, anno 3 - 30/04/2000 - file 24 di 28 ]-------------
==============================================================================
-[ MiSCELLANE0US ]------------------------------------------------------------
---[ ARRAY E PUNTAT0Ri
-----[ |scacco| a.k.a. Marcello Scacchetti <scacco@s0ftpj.org>
Eccoci qui carissimi lettori di BFi. Questa volta il vostro scaccone preferito
vi presenta un bell'articolo sulla programmazione in c riguardante i classici
problemi che incontrano i nuovi arrivati, cioe' gli array ed i puntatori.
Tutti coloro che provengono da linguaggi di alto livello come ad esempio il
Visual Basic non sono abituati a considerare l'utilizzo della memoria nelle
loro applicazioni: basta infatti un bel Dim pippo as String per sistemare le
cose. Bene... spaventatevi: in c le cose si complicano notevolmente. Quando si
vuole il controllo del sistema bisogna saperlo controllare (uhm... orribile
frase, ma significativa). In questo articolo affronteremo alcuni argomenti
must del c; chi fosse gia' esperto in questo linguaggio puo' saltare le parti
iniziali. Passiamo subito alla pratica con un bel programmino:
<-| scaes01.c |->
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i,j;
char k;
char *l;
i = 10;
j = 20;
k = 'M';
l = "Ciao Mondo";
printf("La dimensione di una variabile intera e': %d byte\n", sizeof(int));
printf("La dimensione di una variabile char e': %d byte\n", sizeof(char));
printf("La dimensione di un puntatore char e': %d byte\n", sizeof(char *));
printf("Il valore della variabile i e': %d\n", i);
printf("Il valore della variabile j e': %d\n", j);
printf("Il valore della variabile k e': %c\n", k);
printf("Il valore della variabile l e': %s\n", l);
printf("Il valore della locazione di memoria contente i e': %x\n", &i);
printf("Il valore della locazione di memoria contente j e': %x\n", &j);
printf("Il valore della locazione di memoria contente k e': %x\n", &k);
printf("Il valore della locazione di memoria contente l e': %x\n", &l);
printf("Il valore esadecimale di i e': %x\n", i);
printf("Il valore esadecimale di j e': %x\n", j);
printf("Il valore esadecimale di k e': %x\n", k);
return 0;
}
<-X->
Sfruttiamo questo esempio per analizzare in dettaglio la gestione della
memoria del c. In questo programma vengono dichiarate 4 variabili,
precisamente 2 interi, 1 carattere e 1 puntatore di tipo carattere. Queste
quattro variabili vengono inizializzate con valori casuali per poterle
analizzare in seguito. Notiamo subito una piccola Tip:
k = 'M';
l = "Ciao Mondo";
Vi starete chiedendo: Come mai non hai utilizzato la forma k = "M" ?
Prima di tutto bisogna ricordare che in c le stringhe vengono chiamate NULL
terminated cioe' quando si fa riferimento ad una stringa si intende un
array di caratteri terminante in un carattere nullo (NULL). Come mai tutto
cio'? Semplice, quando il vostro programma verra' istruito ad andare a leggere
una stringa in memoria partendo da una determinata locazione terminera' di
leggere la stringa leggendo il primo carattere NULL. Questo e' anche il
principio di massima su cui si basano i buffer overflow: se fosse possibile
sovrascrivere il carattere NULL si potrebbe far andare l'esecuzione in
qualsiasi punto della memoria. Se vi interessano dettagli maggiori su questo
argomento fate riferimento a FuSyS o del0rean. Tornando a noi, in c quando si
vuole utilizzare una stringa la si pone tra "" e questo dice al compilatore di
aggiungere un carattere NULL automaticamente al termine della mia stringa. Se
fate attenzione k e' definito come carattere, come un carattere, se avessi
usato le "" il compilatore avrebbe tentato di aggiungere NULL al valore M
creando una stringa di 2 caratteri, andando quindi contro alla dichiarazione
char. Ecco risolto l'enigma delle virgolette. Passiamo oltre. I successivi tre
printf mostrano sul sistema corrente le dimensioni in byte dei tipi di
variabili. Su una architettura intel x86 il risultato dovrebbe essere:
La dimensione di una variabile intera e': 4 byte
La dimensione di una variabile char e': 1 byte
La dimensione di un puntatore char e': 4 byte
Come potete immaginare quindi i valori di un numero intero di estendono da
-2.147.483.647 a 2.147.483.647 in quanto 4 byte corrispondono a 32 bit; se
provate a convertire 11111111111111111111111111111111 da binario a decimale
otterrete 4.294.967.295: notate che questo valore e' esattamente la somma
dei range in valore assoluto, infatti il primo bit a sinistra in un normale
intero e' utilizzato per dare il segno e se riprovate infatti a fare la
conversione utilizzando 31 bit invece di 32 noterete che il risultato e'
2.147.483.647... i conti tornano. Se vorrete utilizzare un intero solo per
valori positivi potrete utilizzare il suffisso unsigned: cio' dira' al
compilatore di considerare il primo bit a sinistra non piu' come il segno del
valore, ma come parte integrante del valore stesso.
Per quanto riguarda il tipo char il valore e' 1 byte, cioe' 8 bit; un
carattere potra' avere unicamente la dimensione di 1 byte.
Una variabile puntatore di tipo carattere occupa 4 byte cioe' 32 bit e questo
valore a 32 bit contiene l'indirizzo della locazione di memoria puntata dalla
variabile. Come e' facile da immaginare si avranno a disposizione
4.294.967.295 valori per l'indirizzo della memoria.
Ora che sappiamo le dimensioni dei tipi di dati possiamo vedere nel caso
pratico cosa accade alla memoria del nostro sistema. Osserviamo prima di tutto
l'output del programma precedente:
Il valore della variabile i e': 10
Il valore della variabile j e': 20
Il valore della variabile k e': M
Il valore della variabile l e': Ciao Mondo
Come potete vedere i due interi contengono rispettivamente i valori decimali
10 e 20, la variabile carattere contiene la lettera M (notare che per
carattere non si intende solamente un valore alfabetico, ma anche numerico o
speciale, carattere e' anche 1 oppure ! oppure \). La stringa puntata dal
puntatore e' Ciao Mondo come da inizializzazione. Andiamo a vedere come questi
dati vengano disposti in memoria osservando il successivo output:
Il valore della locazione di memoria contente i e': bffffd44
Il valore della locazione di memoria contente j e': bffffd40
Il valore della locazione di memoria contente k e': bffffd3f
Il valore della locazione di memoria contente l e': bffffd38
bffffd** e' l'indirizzo esadecimale della memoria; come potete notare, avendo
un intero dimensione 4 byte, gli indirizzi delle due variabili intere sono
distanziati di 4 posizioni: cio' evidenzia il fatto che una locazione di
memoria e' 1 byte cioe' 8 bit. Apriamo il gdb (GNU Debug) il nostro fidato
compagno di avventura... settiamo un breakpoint sulla funzione printf tramite
il comando break printf ed eseguiamo un run. Appena avviene l'interruzzione
andiamo tramite il comando x ad analizzare il contenuto della memoria:
(gdb) x 0xbffffd47
0xbffffd47: 0xfffd6800
(gdb) x 0xbffffd46
0xbffffd46: 0xfd680000
(gdb) x 0xbffffd45
0xbffffd45: 0x68000000
(gdb) x 0xbffffd44
0xbffffd44: 0x0000000a
(gdb) x 0xbffffd43
0xbffffd43: 0x00000a00
(gdb) x 0xbffffd42
0xbffffd42: 0x000a0000
(gdb) x 0xbffffd41
0xbffffd41: 0x0a000000
(gdb) x 0xbffffd40
0xbffffd40: 0x00000014
(gdb) x 0xbffffd3f
0xbffffd3f: 0x0000144d
(gdb) x 0xbffffd3e
0xbffffd3e: 0x00144d04
(gdb) x 0xbffffd3d
0xbffffd3d: 0x144d0483
(gdb) x 0xbffffd3c
0xbffffd3c: 0x4d0483bb
(gdb) x 0xbffffd3b
0xbffffd3b: 0x0483bb08
(gdb) x 0xbffffd3a
0xbffffd3a: 0x83bb0804
(gdb) x 0xbffffd39
0xbffffd39: 0xbb080485
(gdb) x 0xbffffd38
0xbffffd38: 0x08048580
Come mai ho attivato il debug dalla locazione di memoria 0xbffffd47 e non da
0xbffffd44? Se ricordate abbiamo detto precedentemente che una variabile di
tipo integer occupa 4 byte in memoria. Ogni locazione di memoria e'
esattamente 1 byte, infatti il range di memoria occupato dal primo integer,
per capirci la variabile i, parte da 0xbffffd44 e termina a 0xbffffd47. La
stessa cosa vale anche per il secondo integer che essendo di 4 byte avra' un
range di 4 locazioni di memoria, da 0xbffffd40 a 0xbffffd43. Si puo' fare lo
stesso discorso anche per la variabile carattere k: come detto precedentemente
una variabile carattere occupa 1 solo byte in memoria e per questo motivo le
e' stato dedicata solo 1 locazione di memoria cioe': 0xbffffd3f.
Dalla locazione 0xbffffd38 alla locazione 0xbffffd3e la memoria e' occupata
dal puntatore di tipo carattere. In realta' dalla locazione 0xbffffd38 alla
0xbffffd3e non viene registrata la stringa, ma dei nuovi indirizzi che
puntano alla regione di memoria data dove e' stata registrata la stringa.
Estrapoliamo dall'output di gdb i dati fondamentali:
Variabile Range Locazioni Valore Valore Hex
i 0xbffffd44-0xbffffd47 10 A
j 0xbffffd40-0xbffffd43 20 14
k 0xbffffd3f-0xbffffd3f M 4D
l 0xbffffd38 Indirizzo 0x08048580
Andiamo ora ad osservare tramite il nostro fidato gdb la posizione in memoria
della stringa. Eseguendo:
(gdb) x 0xbffffd38
0xbffffd38: 0x08048580
Ottengo il valore esadecimale 0x08048580 che e' il nuovo indirizzo di
riferimento per la sezione di memoria data. Eseguendo:
(gdb) x 0x08048580
0x8048580 <_IO_stdin_used+28>: 0x6f616943
Ottengo il valore esadecimale 0x6f616943 che corrisponde in codice ASCII al
valore: "oaiC" se provate a routare la stringa otterrete Ciao che e' la
prima parte della nostra stringa. Proseguiamo ora ad analizzare la memoria
nella sezione data tramite il comando:
(gdb) x 0x08048581
0x8048581 <_IO_stdin_used+29>: 0x206f6169
Ottengo cosi' il valore ASCII " oai", cioe' spostandoci di un byte in avanti
nella memoria stiamo scorrendo ogni singolo carattere della stringa. Se
pensate a quello che abbiamo detto prima, ovvero che un indirizzo di memoria
contiene 1 byte ed una variabile carattere vale 1 byte, il risultato e'
corretto. Ora eseguiamo qualche comando in successione:
(gdb) x 0x08048582
0x8048582 <_IO_stdin_used+30>: 0x4d206f61
(gdb) x 0x08048583
0x8048583 <_IO_stdin_used+31>: 0x6f4d206f
(gdb) x 0x08048584
0x8048584 <_IO_stdin_used+32>: 0x6e6f4d20
(gdb) x 0x08048585
0x8048585 <_IO_stdin_used+33>: 0x646e6f4d
(gdb) x 0x08048586
0x8048586 <_IO_stdin_used+34>: 0x6f646e6f
(gdb) x 0x08048587
0x8048587 <_IO_stdin_used+35>: 0x006f646e
(gdb) x 0x08048588
0x8048588 <_IO_stdin_used+36>: 0x00006f64
(gdb) x 0x08048589
0x8048589 <_IO_stdin_used+37>: 0x0000006f
(gdb) x 0x0804859a
0x804859a <_IO_stdin_used+54>: 0x00000000
Creiamo una piccola tabella per vedere ad ogni locazione il risultato
esadecimale e ASCII:
Locazione Valore Esadecimale Valore ASCII
0x08048580 0x6f616943 "oaiC"
0x08048581 0x206f6169 " oai"
0x08048582 0x4d206f61 "M oa"
0x08048583 0x6f4d206f "oM o"
0x08048584 0x6e6f4d20 "noM "
0x08048585 0x646e6f4d "dnoM"
0x08048586 0x6f646e6f "odno"
0x08048587 0x006f646e " odn"
0x08048588 0x00006f64 " od"
0x08048589 0x0000006f " o"
0x0804859a 0x00000000 " "
Piccola, ma fondamentale notazione ASCII / Esadecimale: Notate la differenza
tra il valore esadecimale 20 e il valore 00. Convertendo in ASCII si otterra'
nel primo caso il carattere spazio e nel secondo un valore nullo. Se guardate
la tabella alla locazione 0x08048589 dopo il valore 67 che corrisponde a "o"
in ASCII c'e' il valore 00. Come detto prima 00 e' nullo cioe' NULL terminating
string. Ora dovrebbe essere tutto molto piu' chiaro su come venga determinato
il termine di una stringa.
In questa prima parte dell'articolo avete imparato in dettaglio ad analizzare
la memoria per cercare le informazioni in fase di debug ed analisi. D'ora in
poi daro' per scontato il fatto che riusciate ad analizzare la memoria e non
mi dilungero' sulle istruzioni di gdb. Realizziamo ora un altro programmino
dimostrativo per vedere come in realta' possono essere utilizzati i puntatori
in programmi reali e quali sono i loro effetti.
<-| scaes02.c |->
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int i,j;
i = 10;
j = 20;
printf("La locazione di memoria di j e': 0x%x\n", &j);
printf("Il valore di j prima della funzione e': %d\n",j);
modify_it(&j);
printf("Il valore di j dopo la funzione e': %d\n",j);
printf("La locazione di memoria di j dopo la funzione e': 0x%x\n", &j);
printf("La locazione di memoria di i e': 0x%x\n", &i);
printf("Il valore di i prima della funzione e': %d\n",i);
leave_it(i);
printf("Il valore di i dopo la funzione e': %d\n",i);
printf("La locazione di memoria di i dopo la funzione e': 0x%x\n", &i);
return 0;
}
modify_it(int *j)
{
printf("La locazione di memoria di j all'interno della funzione e': 0x%x\n",j);
*j = 10;
printf("Il valore di j all'interno della funzione e': %d\n",*j);
}
leave_it(int i)
{
printf("La locazione di memoria di i all'interno della funzione e': 0x%x\n",&i);i = 20;
printf("Il valore di i all'interno della funzione e': %d\n",i);
}
<-X->
Traduciamo questo codice in pseudo codice:
dichiara le variabili i e j come interi;
assegna ad i il valore 10;
assegna a j il valore 20;
visualizza la locazione di memoria di j;
visualizza il valore di j;
chiama la funzione modify_it passando come argomento l'indirizzo di j;
visualizza il valore di j dopo la funzione;
visualizza la locazione di memoria di j dopo la funzione;
visualizza la locazione di memoria di i;
visualizza il valore di i;
chiama la funzione leave_it passando come argomento il valore di i;
visualizza il valore di i dopo la funzione;
visualizza la locazione di memoria di i dopo la funzione;
funzione modify_it parametro puntatore intero j
visualizza la locazione di memoria di j;
assegna alla memoria puntata dal puntatore il valore 10;
visualizza il valore di j;
fine funzione modify_it
funzione leave_it parametro intero i
visualizza la locazione di memoria di i;
assegna alla variabile i il valore 10;
visualizza il valore di i;
fine funzione leave_it
Proviamo ad eseguire il programma, l'output dovrebbe essere simile a:
[scacco@marcello arrpoint]$ ./2
La locazione di memoria di j e': 0xbffffd40
Il valore di j prima della funzione e': 20
La locazione di memoria di j all'interno della funzione e': 0xbffffd40
Il valore di j all'interno della funzione e': 10
Il valore di j dopo la funzione e': 10
La locazione di memoria di j dopo la funzione e': 0xbffffd40
La locazione di memoria di i e': 0xbffffd44
Il valore di i prima della funzione e': 10
La locazione di memoria di i all'interno della funzione e': 0xbffffd3c
Il valore di i all'interno della funzione e': 20
Il valore di i dopo la funzione e': 10
La locazione di memoria di i dopo la funzione e': 0xbffffd44
[scacco@marcello arrpoint]$
Vediamo ora graficamente cosa e' successo alla memoria con l'ausilio di gdb:
Locazione Memoria Valore Esadecimale Valore Decimale
0xbffffd40 0x00000014 20
0xbffffd44 0x0000000a 10
0xbffffd3c 0x00000014 20
Stato memoria dopo la dichiarazione variabili e assegnamento valori
___________________________________________________________________
/ \
0xbffffd40 -> 0x00000014 ----
0xbffffd41 -> 0x0a000000 \ Variabile j (4 byte)
0xbffffd42 -> 0x000a0000 /
0xbffffd43 -> 0x00000a00 ----
0xbffffd44 -> 0x0000000a ----
0xbffffd45 -> 0x68000000 \ Variabile i (4 byte)
0xbffffd46 -> 0xfd680000 /
0xbffffd47 -> 0xfffd6800 ----
\ ___________________________________________________________________ /
Stato memoria dopo la funzione modify_it
___________________________________________________________________
/ \
0xbffffd40 -> 0x0000000a ----
0xbffffd41 -> 0x0a000000 \ Variabile j (4 byte)
0xbffffd42 -> 0x000a0000 /
0xbffffd43 -> 0x00000a00 ----
0xbffffd44 -> 0x0000000a ----
0xbffffd45 -> 0x68000000 \ Variabile i (4 byte)
0xbffffd46 -> 0xfd680000 /
0xbffffd47 -> 0xfffd6800 ----
\ ___________________________________________________________________ /
Stato memoria durante la funzione leave_it
___________________________________________________________________
/ \
0xbffffd3c -> 0x00000014 ----
0xbffffd3d -> 0x0a000000 \ Variabile i (4 byte) locale
0xbffffd3e -> 0x000a0000 /
0xbffffd3f -> 0x00000a00 ----
0xbffffd40 -> 0x0000000a ----
0xbffffd41 -> 0x0a000000 \ Variabile j (4 byte)
0xbffffd42 -> 0x000a0000 /
0xbffffd43 -> 0x00000a00 ----
0xbffffd44 -> 0x0000000a ----
0xbffffd45 -> 0x68000000 \ Variabile i (4 byte)
0xbffffd46 -> 0xfd680000 /
0xbffffd47 -> 0xfffd6800 ----
\ ___________________________________________________________________ /
Ora abbiamo tutti i dati per una buona analisi dell'accaduto. La funzione
modify_it, come potete notare dal codice, agisce direttamente sulla locazione
di memoria del puntatore della variabile j, mentre la funzione leave_it
lavora unicamente sul valore della variabile i. All'interno della funzione
modify_it con l'istruzione *j = 10; sono andato a sovrascrivere la memoria
di j inserendo il nuovo valore cioe' 10. Come potete notare non ho
inizializzato nessuna nuova variabile all'interno di modify_it o meglio non
ho allocato nessun ulteriore spazio. All'interno di leave_it invece ho allocato
spazio per una nuova variabile che ovviamente ha assunto una locazione di
memoria vuota, in questa locazione ho copiato il valore originale di i e lo
ho modificato in 20. All'uscita da questa funzione il valore originale di i
e' rimasto invariato in quanto la locazione di memoria e' stata solo coinvolta
in lettura.
Ora conosciamo bene i puntatori per poter affrontare l'argomento array e
vedere quali sono le differenze e come mai spesso vengono erroneamente
confusi.
Prima di tutto un array e' una serie di locazioni di memoria contigue contenti
lo stesso tipo di dati ed in cui l'indirizzo di memoria piu' basso corrisponde
al primo elemento dell'array, quello piu' alto all'ultimo. Abbiamo gia' visto
un array precedentemente senza accorgercene: una stringa non e' altro che un
array di caratteri, cioe' tante locazioni di memoria contigue contenenti delle
variabili char delimitate da un carattere NULL. La differenza che salta agli
occhi tra array e puntatori e' che il primo si riferisce alla memoria, mentre
il secondo all'indirizzo della memoria. Normalmente si tendono a confondere le
cose quando si parla di array di puntatori, pertanto creiamo un altro
programma per mostrare la situzione:
<-| scaes03.c |->
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char array[] = "Ciao Mondo";
char *pointer;
char *pointarr[30];
pointer = "Ciao Mondo";
pointarr[0] = "Ciao Mondo a 0";
pointarr[1] = "Ciao Mondo a 1";
pointarr[20] = "Ciao Mondo a 20";
printf("Analizzo la variabile array...\n");
analyze_array(&array);
printf("Analizzo la variabile pointer...\n");
analyze_pointer(&pointer);
printf("Analizzo la variabile pointarr...\n");
analyze_pointarr(&pointarr);
return 0;
}
analyze_array(char arr[])
{
int i;
printf("La locazione di memoria della variabile arr e': 0x%x\n", &arr);
for(i = 0; i <10; i++)
{
printf("La locazione di memoria della variabile array[%d] e': 0x%x\n",i,&arr[i]);
printf("Il carattere contenuto in array[%d] e': %c\n",i,arr[i]);
}
}
analyze_pointer(char *pointer)
{
printf("La locazione di memoria di pointer e': 0x%x\n", pointer);
printf("Il contenuto di pointer e': %s\n", pointer);
}
analyze_pointarr(char *pointer[])
{
printf("La locazione di memoria di pointer e': 0x%x\n", pointer);
printf("La locazione di memoria della variabile pointer[0] e': 0x%x\n", &pointer[0]);
printf("Il contenuto di pointer[0] e': %s\n", pointer[0]);
printf("La locazione di memoria della variabile pointer[1] e': 0x%x\n", &pointer[1]);
printf("Il contenuto di pointer[1] e': %s\n", pointer[1]);
printf("La locazione di memoria della variabile pointer[20] e': 0x%x\n", &pointer[20]);
printf("Il contenuto di pointer[20] e': %s\n", pointer[20]);
}
<-X->
Eseguendo questo programmino l'output dovrebbe essere qualcosa di simile a:
[scacco@marcello arrpoint]$ ./3
Analizzo la variabile array...
La locazione di memoria della variabile arr e': 0xbffffcbc
La locazione di memoria della variabile array[0] e': 0xbffffd3c
Il carattere contenuto in array[0] e': C
La locazione di memoria della variabile array[1] e': 0xbffffd3d
Il carattere contenuto in array[1] e': i
La locazione di memoria della variabile array[2] e': 0xbffffd3e
Il carattere contenuto in array[2] e': a
La locazione di memoria della variabile array[3] e': 0xbffffd3f
Il carattere contenuto in array[3] e': o
La locazione di memoria della variabile array[4] e': 0xbffffd40
Il carattere contenuto in array[4] e':
La locazione di memoria della variabile array[5] e': 0xbffffd41
Il carattere contenuto in array[5] e': M
La locazione di memoria della variabile array[6] e': 0xbffffd42
Il carattere contenuto in array[6] e': o
La locazione di memoria della variabile array[7] e': 0xbffffd43
Il carattere contenuto in array[7] e': n
La locazione di memoria della variabile array[8] e': 0xbffffd44
Il carattere contenuto in array[8] e': d
La locazione di memoria della variabile array[9] e': 0xbffffd45
Il carattere contenuto in array[9] e': o
Analizzo la variabile pointer...
La locazione di memoria di pointer e': 0xbffffd38
Il contenuto di pointer e': Ciao Mondo
Analizzo la variabile pointarr...
La locazione di memoria di pointer e': 0xbffffcc0
La locazione di memoria della variabile pointer[0] e': 0xbffffcc0
Il contenuto di pointer[0] e': Ciao Mondo a 0
La locazione di memoria della variabile pointer[1] e': 0xbffffcc4
Il contenuto di pointer[1] e': Ciao Mondo a 1
La locazione di memoria della variabile pointer[20] e': 0xbffffd10
Il contenuto di pointer[20] e': Ciao Mondo a 20
[scacco@marcello arrpoint]$
Osservando attentamente la funzione di analisi dell'array si puo' notare che
ad ogni elemento dell'array corrisponde un singolo carattere e possono essere
referenziati singolarmente tramite la sintassi variabile[num]. Come abbiamo
detto piu' di una volta il tipo di dati char occupa 1 solo byte in memoria e
quindi ogni elemento e' distanziato di 1 locazione di memoria. Se si specifica
un elemento al di fuori dell'array il comportamento non e' definito in quanto
si andra' a leggere memoria non inizializzata che potra' contenere qualsiasi
valore preesistente. Nel caso del puntatore stringa le cose sono differenti:
il limite della memoria non e' piu' dato dalla lettura, ma dal valore in se'.
Per capirci: nel caso di un array di char se voi eseguite la lettura
all'esterno dell'area inizializzata sapete di causare una lettura bogus,
mentre nel caso della lettura stringa da pointer, se non e' specificato
all'interno della stringa stessa il carattere NULL finale, la lettura prosegue
nella memoria alla ricerca di questo carattere. Se siete fortunati lo potreste
trovare subito dopo, ma nella maggior parte dei casi si andra' a leggere o
scrivere qualcosa di non prevedibile. Per questo motivo molti programmatori
usano creare routine particolari che controllano la presenza del carattere
NULL alla fine della stringa dopo ogni inserimento o lettura dati. Queste
routine particolari si chiamano sanity checks. Proviamo ora a realizzare un
programmino che contenga un sanity check basilare:
<-| scaes04.c |->
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char array[] = "Ciao Mondo";
char arraybogus[20];
arraybogus[0] = 'a';
arraybogus[1] = 'b';
arraybogus[2] = 'c';
printf("Il valore della stringa contenuta nell'array e': %s\n", (char *)array);
printf("Il valore della stringa contenuta nell'array bogus e': %s\n", (char *)arraybogus);
sanitycheck(&arraybogus);
printf("Il valore della stringa contenuta nell'array bogus dopo il sanity check
e': %s\n", (char *)arraybogus);
return 0;
}
sanitycheck(char arraybogus[])
{
if(arraybogus[3] != '\0') {
printf("La variabile arraybogus non e' sana...\n");
printf("Aggiungo un null terminating...\n");
arraybogus[3] = '\0';
}
}
<-X->
Eseguendo questo nuovo programma si dovrebbe ottenere:
[scacco@marcello arrpoint]$ ./4
Il valore della stringa contenuta nell'array e': Ciao Mondo
Il valore della stringa contenuta nell'array bogus e': abc@xxx
La variabile arraybogus non e' sana...
Aggiungo un null terminating...
Il valore della stringa contenuta nell'array bogus dopo il sanity check e': abc
[scacco@marcello arrpoint]$
Questo esempio dovrebbe rappresentare chiaramente le conseguenze di una errata
indicizzazione di un array. Guardando il sorgente potete vedere come ho
inizializzato due array, di cui il primo e' array ed il secondo e' arraybogus.
In array ho messo una stringa tra virgolette e quindi ho istruito il
compilatore ad aggiungere un null char al termine della stringa stessa in modo
automatico. Nel secondo caso invece ho inserito a mano i valori all'interno
dell'array omettendo appositamente il NULL char. Il primo printf contiene un
casting da array di caratteri a stringa; in questo caso funziona perfettamente
in quanto, come detto precedentemente, questo array di caratteri ha un
terminatore NULL. Nel secondo printf il casting restituisce risultati errari
in quanto il programma continua a leggere in memoria ed a stampare fino a
quando non incontra il primo NULL char. Successivamente viene richiamata la
funzione sanitycheck il cui scopo non e' altro che controllare se il quarto
elemento dell'array contiene un NULL char: se non lo contiene lo va ad
aggiungere alla locazione di memoria originale. Eseguendo quindi il terzo
casting tutto torna a funzionare nuovamente in modo corretto.
Per questo numero e' tutto. Nel prossimo articolo vi proporro' argomenti un
attimino piu' avanzati come strutture, puntatori funzioni, linked lists e
company. A presto!
Ringraziamenti:
smaster: Er Boss!
pIGpEN: *BSD Master
FuSyS: Protocol Master
bELFa: The neverending man
Naild0d: grazie mille!!!
vecna: il compagno di ventura
InfectedM: anche lui
\sPIRIT\ (o varianti): win32 guru
BBerry: un volto alle news
|SquaY2K|: Il mio fan piu' accanito
s0ftpj: grazie a tutti!
maruz, buzzzo, velenux, why e tutti quelli di #linux-it
grazie anche a tutti quelli che ho dimenticato... scusate :(
==============================================================================
--------------------------------[ EOF 24/28 ]---------------------------------
==============================================================================