============================================================================== ------------[ BFi numero 8, anno 3 - 30/04/2000 - file 24 di 28 ]------------- ============================================================================== -[ MiSCELLANE0US ]------------------------------------------------------------ ---[ ARRAY E PUNTAT0Ri -----[ |scacco| a.k.a. Marcello Scacchetti 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 #include 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 #include 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 #include 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 #include 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 ]--------------------------------- ==============================================================================