sabato 6 settembre 2008

Linguaggio C: uno sguardo ai puntatori e agli array.

Il linguaggio di programmazione in assoluto più versatile e quindi usato, almeno in ambiente UNIX/LINUX è sicuramente il C. Anche se non così fortemente tipizzato come altri linguaggi (ad esempio Java e Pascal), il C è avido di dichiarazioni e definizioni precise e inoltre non possiede il tipo stringa, che quindi deve essere implementato con gli Array di caratteri o, equivalentemente, grazie ai puntatori.
In generale se si ha a che fare con input e stringhe le inclusioni da fare sono:

#include<stdio.h>  // per la gestione dell'input (scanf), dell'output (printf) e
// dei files (macro FILE)

#include<stdlib.h> // per le funzioni di gestione dinamica della memoria
#include<ctype.h> // per le funzioni per la classificazione dei caratteri
#include<string.h> // per la manipolazione delle stringhe

Veniamo al modo in cui si definisce una stringa in C.
Una possibilità è quella di usare i puntatori a carattere. Definiamo un puntatore a carattere al modo solito:
char* stringa;

A questo punto necessitiamo di allocare della memoria in modo che il compilatore sappia quante celle lasciare libere per la nostra stringa. Per fare questo si usa la funzione della libreria standard (in stdlib.h si trova il suo prototipo) malloc che si occupa di allocare una certa quantità di memoria dinamicamente. A programma terminato dovremo liberare la memoria allocata con la funzione free.
stringa = malloc(64);

free(stringa)

Un'altra possibilità si ha usando gli array di caratteri. Gli array di caratteri sono delle liste ordinate di caratteri, cioè di dati tipo char. Di tale lista deve essere dichiarata in anticipo la lunghezza, in questo modo:
char array[64];  //  lista di 64 caratteri

Ciò significa che in fase di compilazione il compilatore allocherà 64 spazi liberi contigui di memoria per contenere i caratteri del nostro array.
A questo punto la cosa migliore da fare è analizzare del codice di prova:
#include<stdio.h>
#include<stdlib.h>
#include<ctype.h>
#include<string.h>

// Variabili
char array[64];
char* stringa;
int j, k, count;

int main(){
printf("Scrivi una stringa con i puntatori: ");
stringa = malloc(64);
scanf("%s", stringa);
count = strlen(stringa);
printf("Indirizzi di memoria usati:\n");
for( j = 0 ; j < count ; j++ ){
printf("%d° indirizzo - %X \n", (j+1), (stringa+j));
}
printf("Contenuto stringa: %s\n", stringa);
printf("Indirizzo puntato memorizzato in: %X\n", &stringa);
for( k = 0 ; k < count ; k++ ) {
printf("%d° carattere della stringa: *(stringa+%d) -> %c\n", k+1, k, *(stringa+k));
}
free(stringa);
printf("\nScrivi una stringa con gli array: ");
scanf("%s", array);
count = strlen(array);
printf("Indirizzi di memoria usati:\n");
for( j = 0 ; j < count ; j++ ){
printf("%d° indirizzo - %X \n", (j+1), array+j);
}
printf("Contenuto stringa: %s\n", array);
printf("Primo indirizzo puntato con il nome dell'array: %X\n", array);
for( k = 0 ; k < count ; k++ ) {
printf("%d° carattere della stringa: array[%d] -> %c\n", k+1, k, array[k]);
}
return 0;
}

L'output del programma è il seguente:
Scrivi una stringa con i puntatori: prova1
Indirizzi di memoria usati:
1° indirizzo - 804A008
2° indirizzo - 804A009
3° indirizzo - 804A00A
4° indirizzo - 804A00B
5° indirizzo - 804A00C
6° indirizzo - 804A00D
Contenuto stringa: prova1
Indirizzo puntato memorizzato in: 8049AA4
1° carattere della stringa: *(stringa+0) -> p
2° carattere della stringa: *(stringa+1) -> r
3° carattere della stringa: *(stringa+2) -> o
4° carattere della stringa: *(stringa+3) -> v
5° carattere della stringa: *(stringa+4) -> a
6° carattere della stringa: *(stringa+5) -> 1

Scrivi una stringa con gli array: prova2
Indirizzi di memoria usati:
1° indirizzo - 8049A60
2° indirizzo - 8049A61
3° indirizzo - 8049A62
4° indirizzo - 8049A63
5° indirizzo - 8049A64
6° indirizzo - 8049A65
Contenuto stringa: prova2
Primo indirizzo puntato con il nome dell'array: 8049A60
1° carattere della stringa: array[0] -> p
2° carattere della stringa: array[1] -> r
3° carattere della stringa: array[2] -> o
4° carattere della stringa: array[3] -> v
5° carattere della stringa: array[4] -> a
6° carattere della stringa: array[5] -> 2

Da questo semplice programma impariamo come una stringa può essere definita con puntatori e con array. La variabile stringa, cioè il nome del puntatore, punta all'indirizzo del primo carattere della stringa, mentre usando stringa+1, si punta al secondo carattere e così via. Discorso analogo vale per array, in modo che scrivere array+3 equivale a puntare al quarto elemento dell'array.
Se invece si vuole scrivere il contenuto di un singolo carattere, nel caso dei puntatori, si deve usare l'operatore di indirezione *. Allora *stringa contiene il valore del primo carattere puntato, *(stringa+1) il valore del secondo e così via.
Nel caso degli array basta scrivere array[0] per il primo carattere della stringa, array[1] per il secondo e così via.
Le funzioni che accettano stringhe come argomenti dovranno ricevere solo il nome del puntatore o il nome dell'array e automaticamente capiranno che devono considerare il carattere puntato da tale nome e le allocazioni di memoria ad esso contigue fino al carattere di terminazione di stringa \0.
Per quanto riguarda printf e la scanf, si noti come in corrispondenza del nome del puntatore (stesso discorso vale per gli array) si ottiene la stringa intera usando l'istruzione di formattazione %s, mentre si ottiene l'indirizzo di memoria puntato usando l'istruzione di formattazione %X. Analogamente usando l'istruzione di formattazione %c, in corrispondenza di *(stringa+2) nel caso dei puntatori e in corrispondenza di array[2] nel caso degli array, prinft stampa a video il terzo carattere della stringa.
Notare che se non si allocasse memoria con malloc il programma verrebbe compilato comunque se privo di altri errori ma in fase di esecuzione andrebbe in segmentation fault, tipico errore che si ha quando il sistema non trova spazio in memoria allocato per la variabile che sta chiamando oppure quando va a scrivere fuori dal segmento di memoria assegnato, magari pure sovrascrivendo altre variabili (grazie Packz).
Spero che questo post sia abbastanza chiaro anche se mi rendo conto che non esaurisce le problematiche intorno alle stringhe e al loro utilizzo nel linguaggio C. Voi che ne dite? A presto!

0 commenti: