Barninga Z
a- a+

I puntatori C: le stringhe

PUNTATORI 

Le stringhe 

Abbiamo anticipato che non esiste, in C, il tipo di dato"stringa". Queste sono gestite dal compilatore comesequenze di caratteri, cioè di dati di tipochar. Un metodo comunemente utilizzato perdichiarare e manipolare stringhe nei programmi èofferto proprio dai puntatori, come si vede nel programmadell'esempio seguente, che visualizza "CiaoCiao!" e porta a capo il cursore. 

#includechar *string = "Ciao";void main(void){    printf(string);    printf(" %s!" ,string);}

La dichiarazione di string può apparire, aprima vista, anomala. Si tratta infatti, a tutti gli effetti,della dichiarazione di un puntatore e la stranezza consistenel fatto che a questo non è assegnato un indirizzo dimemoria, come ci si potrebbe aspettare, bensì unacostante stringa. Ma è proprio questo l'artificioche consente di gestire le stringhe con normali puntatori acarattere: il compilatore, in realtà, assegna astring, puntatore a 16 bit, l'indirizzo dellacostante "Ciao". Dunque la word occupatada string non contiene la parola"Ciao", ma i 16 bit che esprimono la parteoffset del suo indirizzo. A sua volta,"Ciao" occupa 5 byte di memoria. Proprio5, non si tratta di un errore di stampa: i 4 byte necessari amemorizzare i 4 caratteri che compongono la parola,più un byte, nel quale il compilatore memorizza ilvalore binario 0, detto terminatore di stringa onull terminator. In C, tutte le stringhe sono chiuseda un null terminator, ed occupano perciò un byte inpiù del numero di caratteri "stampabili" chele compongono. 

La prima chiamata a printf() passa quale argomentoproprio string: dunque la stringa parametroindispensabile di printf() non deve esserenecessariamente una stringa di formato quando l'unicacosa da visualizzare sia proprio una stringa. Lo è,però, quando devono essere visualizzati caratteri onumeri, o stringhe formattate in un modo particolare, comeavviene nella seconda chiamata. 

Qui va sottolineato che per visualizzare una stringa conprintf() occore fornirne l'indirizzo, che nelnostro caso è il contenuto del puntatorestring. Se string punta alla stringa"Ciao", che cosa restituiscel'espressione *string? La tentazione dirispondere "Ciao" è forte, ma secosì fosse perché per visualizzare la parolaoccorre passare a printf() string e non*string? Il problema non si poneva con gli esempiprecedenti, perché tutti i puntatori esaminatiindirizzavano un unico dato di un certo tipo. Con ledichiarazioni 

float numero = 12.5;    float *numPtr = №

si definisce il puntatore numPtr e lo si inizializzain modo che contenga l'indirizzo della variabilenumero, la quale, in fondo proprio comestring, occupa più di un byte. In questocaso, però, i 4 byte di numero contengono undato unitariamente considerato. In altre parole, nessuno dei4 byte che la compongono ha significato in sé e persé. Con riferimento a string, al contrario,ogni byte è un dato a sé stante, cioè undato di tipo char: bisogna allora precisare che unpuntatore indirizza sempre il primo byte di tutti quelli checompongono il tipo di dato considerato, se questi sonopiù d'uno. Se ne ricava che stringcontiene, in realtà, l'indirizzo del primocarattere di "Ciao", cioè la'C'. Allora *string non puòche restituire proprio quella, come si può facilmenteverificare con la seguente chiamata aprintf()

printf("%c è il primo carattere..." ,*string);

Non dimentichiamo che le stringhe sono, per il compilatore C,semplici sequenze di char: la stringa del nostroesempio inizia con il char che si trovaall'indirizzo contenuto in string (la'C') e termina con il primo byte nulloincontrato ad un indirizzo uguale o superiore a quello (inquesto caso il byte che segue immediatamente la'o'). 

Per accedere ai caratteri che seguono il primo èsufficiente incrementare il puntatore o, comunque, sommare adesso una opportuna quantità (che rappresental'offset, cioè lo spostamento, dall'inizodella stringa stessa). Vediamo, come al solito, unesempio: 

 

int i = 0;    while(*(string+i) != 0) {  printf("%c" ,*(string+i));  ++i;    }

L'esempio si basa sull'aritmetica dei puntatori,cioè sulla possibilità di accedere ai datimemorizzati ad un certo offset rispetto ad un indirizzosommandovi algebricamente numeri interi. Il ciclo visualizzala stringa "Ciao" in senso verticale.Infatti l'istruzione while (finalmente una"vera" istruzione C!) esegue le istruzioni compresetra le parentesi graffe finché la condizione espressatra le parentesi tonde è vera (se questa èfalsa la prima volta, il ciclo non viene mai eseguito): inquesto caso la printf() è eseguitafinché il byte che si trova all'indirizzocontenuto in string aumentato di iunità è diverso da 0, cioèfinché non viene incontrato il null terminator. Laprintf() visualizza il byte a quello stessoindirizzo e va a capo. Il valore di i èinizialmente 0, pertanto nella prima iterazionel'indirizzo espresso da string non èmodificato, ma ad ogni loop i è incrementatodi 1 (tale è il significatodell'operatore ++, pertanto ad ogni successiva iterazionel'espressione string+i restituiscel'indirizzo del byte successivo a quello appenavisualizzato. Al termine, i contiene il valore4, che è anche la lunghezza della stringa:questa è infatti convenzionalmente pari al numero deicaratteri stampabili che compongono la stringa stessa; ilnull terminator non viene considerato. In altre parole lalunghezza di una stringa è inferiore di 1 alnumero di byte che essa occupa effettivamente in memoria. Lalunghezza di una stringa può quindi essere calcolatacosì: 

unsigned i = 0;    while(*(string+i))  ++i;

La condizione tra parentesi è implicita: non vienespecificato alcun confronto. In casi come questo ilcompilatore assume che il confronto vada effettuato con ilvalore 0, che è proprio quel che fa al nostrocaso. Inoltre, dato che il ciclo si compone di una sola riga(l'autoincremento di i), le graffe non sononecessarie (ma potrebbero essere  utilizzateugualmente[16]). 

Tutta questa chiacchierata dovrebbe avere reso evidente unacosa: quando ad una funzione viene passata una costantestringa, come in 

printf("Ciao!");

il compilatore, astutamente, memorizza la costante da qualcheparte (non preoccupiamoci del "dove" , per ilmomento) e ne passa l'indirizzo. 

Inoltre, il metodo visto poco fa per "prelevare"uno ad uno i caratteri che compongono una stringa vale anchenel caso li si voglia modificare: 

 

char *string = "Rosso";void main(void){    printf(string);    *(string+3) = 'p';    printf(string);}

Il programma dell'esempio visualizza dapprima la parola"Rosso" e poi "Rospo".Si noti che il valore di string non è mutato:esso continua a puntare alla medesima locazione di memoria,ma è mutato il contenuto del byte che si trova ad unoffset di 3 rispetto a quell'indirizzo. Dal momento chel'indirezione di un puntatore a carattere restituisce uncarattere, nell'assegnazione della lettera'p' è necessario esprimerequest'ultima come un char, e pertanto tra apici(e non tra virgolette). La variabile string non acaso è dichiarata all'esterno dimain()

E' possibile troncare una stringa? Sì, bastainserire un NULL dove occorre: 

*(string+2) = NULL;

A questo punto una chiamata a printf()visualizzerebbe la parola "Ro".NULL è una costante manifesta definita inSTDIO.H, e rappresenta lo zero binario; infatti lariga di codice precedente potrebbe essere scrittacosì: 

*(string+2) = 0;

E' possibile allungare una stringa? Sì, basta...essere sicuri di avere spazio a disposizione. Se sisovrascrive il NULL con un carattere, la stringa siallunga sino al successivo NULL. Occorre fare alcuneconsiderazioni: in primo luogo, tale operazione ha senso, disolito, solo nel caso di concatenamento di stringhe (quandocioè si desidera accodare una stringa ad un'altraper produrne una sola, più lunga). In secondo luogo,se i byte successivi al NULL sono occupati da altridati, questi vengono perduti, sovrascritti dai carattericoncatenati alla stringa: l'effetto può esseredisastroso. In effetti esiste una funzione di libreriaconcepita appositamente per concatenare le stringhe: lastrcat(), che richiede due stringhe quali parametri.L'azione da essa svolta consiste nel copiare i byte checompongono la seconda stringa, NULL terminalecompreso, in coda alla prima stringa, sovrascrivendone ilNULL terminale. 

In una dichiarazione come quella di string, ilcompilatore riserva alla stringa lo spazio strettamentenecessario a contenere i caratteri che la compongono,più il NULL. E' evidente che concatenarea string un'altra stringa sarebbe un graveerrore (peraltro non segnalato dal compilatore, perchéesso lascia il programmatore libero di gestire la memoriacome crede: se sbaglia, peggio per lui). Allora, per potereconcatenare due stringhe senza pericoli occorre riservare inanticipo lo spazio necessario a contenere la prima stringa ela seconda... una in fila all'altra. Affronteremo ilproblema parlando di array e di allocazione dinamica dellamemoria. 

Avvertenza: una dichiarazione del tipo: 

char *sPtr;

riserva in memoria lo spazio sufficiente a memorizzare ilpuntatore alla stringa, e non una (ipotetica) stringa. I byteallocati sono 2 se il puntatore è, comenell'esempio, near; mentre sono 4 se èfar o huge. In ogni caso va ricordato cheprima di copiare una stringa a quell'indirizzo bisognaassolutamente allocare lo spazio necessario a contenerla eassegnarne l'indirizzo a sPtr. Anche a questoproposito occorre rimandare gli approfondimenti alle paginein cui esamineremo l'allocazione dinamica dellamemoria. 

E' meglio sottolineare che le librerie standard del Ccomprendono un gran numero di funzioni (dichiarate inSTRING.H) per la manipolazione delle stringhe, cheeffettuano le più svariate operazioni: copiarestringhe o parte di esse (strcpy(),strncpy()), concatenare stringhe (strcat(),strncat()), confrontare stringhe (strcmp(),stricmp()), ricercare sottostringhe o caratteriall'interno di stringhe (strstr(),strchr(), strtok())... insomma, quando sideve trafficare con le stringhe vale la pena di consultare ilmanuale delle librerie e cercare tra le funzioni il cui nomeinizia con "str": forse la soluzione alproblema è già pronta. 

 



Ti potrebbe interessare anche

commenta la notizia