La maggior parte dei linguaggi ti dà un tipo string che conosce la propria lunghezza. Il C no. In C una stringa è solo un array di char con una regola in più: il testo è seguito da un byte con valore zero, scritto '\0' e chiamato terminatore nullo. Tutto il resto sulle stringhe C discende da questa unica convenzione.
Una stringa è un array di char più '\0'
Si dichiara una stringa inizializzando un array di char con un letterale:
L'array è fatto di quattro byte, non tre:
name: +-----+-----+-----+------+
| 'A' | 'd' | 'a' | '\0' |
+-----+-----+-----+------+
indice: 0 1 2 3
sizeof(name) vale 4. Il compilatore ha contato i caratteri, ne ha aggiunto uno per il terminatore e ha dimensionato l'array per te. Quel byte in più è il motivo per cui char name[3] = "Ada"; è un bug in agguato: il testo ci sta ma il terminatore no, quindi nessuna funzione a valle può capire dove finisce la stringa.
Quando vuoi spazio per cambiare il contenuto in seguito, indica tu la dimensione e lascia margine:
char name[32] = "Ada"; /* 3 caratteri, un terminatore, 28 byte liberi */
I byte non usati vengono riempiti di zeri dall'inizializzatore, cosa innocua.
Perché il terminatore conta
Nessuna funzione C riceve una lunghezza. printf("%s", s) riceve un indirizzo e cammina in avanti stampando byte finché non incontra '\0'. Quindi un array di char senza terminatore non è una stringa, e trattarlo come tale legge qualunque memoria si trovi dopo.
La regola da memorizzare: sizeof ti dà lo spazio di memoria, strlen ti dà il testo. Per char buf[32] = "Ada", sizeof buf vale 32 e strlen(buf) vale 3. Rispondono a domande diverse, e confonderli è uno dei bug più comuni in C.
Letterali stringa e array di char
Queste due righe sembrano simili e si comportano in modo molto diverso:
char a[] = "hello"; /* un array tuo, inizializzato dal letterale */
char *p = "hello"; /* un puntatore rivolto al letterale stesso */
a è un array di 6 byte con una copia privata del testo. Puoi modificarlo. p punta dentro i dati stringa di sola lettura del programma; il letterale è condiviso, e scriverci tramite p è comportamento indefinito che di solito va in crash a runtime, non in compilazione.
Scrivi const char * ogni volta che un puntatore punta a un letterale. Così il compilatore rifiuta la scrittura in fase di compilazione invece di lasciarla diventare un crash. Scegliere tra le due forme è in realtà una questione di puntatori e array, trattata nella pagina su puntatori e array.
Un'altra differenza da conoscere: sizeof a vale 6, mentre sizeof p è la dimensione di un puntatore (8 sulla maggior parte dei sistemi), indipendentemente da quanto è lungo il testo.
Stampare e leggere stringhe
%s stampa una stringa; %c stampa un singolo carattere.
Per l'input, evita del tutto gets: è stata rimossa dal linguaggio perché non si può usare in modo sicuro. Usa fgets, che riceve la dimensione del buffer e non va in overflow:
fgets conserva l'a capo che hai premuto, cosa che nella stringa non vuoi quasi mai. La riga con strcspn trova il primo '\n' e lo sovrascrive con un terminatore: un modo compatto e standard per toglierlo.
Scorrere una stringa carattere per carattere
Dato che la fine è segnata e non contata, il ciclo idiomatico controlla il carattere stesso:
La condizione text[i] != '\0' spesso viene abbreviata in text[i], dato che '\0' vale zero e quindi è falso. Sono corrette entrambe; la forma esplicita è più facile da leggere mentre impari.
Puoi scorrere con un puntatore invece che con un indice, in un C altrettanto idiomatico:
Il cast a unsigned char prima di chiamare toupper non è decorazione: le funzioni di <ctype.h> sono indefinite per valori negativi, e un char semplice può essere negativo per i byte sopra 127.
Non puoi copiare una stringa con =
Questo è il muro contro cui sbatte ogni principiante:
char a[10];
a = "hello"; /* error: assignment to expression with array type */
char b[10] = "hi";
char c[10];
c = b; /* errore, per lo stesso motivo */
Il nome di un array non è un valore modificabile, quindi = non ha niente da fare. L'unico punto in cui la scorciatoia funziona è l'inizializzazione nella dichiarazione, che è un'operazione diversa eseguita dal compilatore.
Per copiare a runtime, copia i byte con strcpy di <string.h>:
Lo stesso ragionamento spiega perché == non confronta le stringhe. if (a == b) confronta due indirizzi, quindi è falso per due array diversi che contengono lo stesso testo. Usa invece strcmp(a, b) == 0. Entrambe le funzioni e le loro varianti più sicure sono trattate nelle funzioni sulle stringhe.
Array di stringhe
Una lista di stringhe è un array di puntatori (per testo fisso che leggi soltanto) oppure un array bidimensionale di char (quando ogni voce deve essere modificabile):
L'array di puntatori memorizza tre indirizzi verso letterali di sola lettura: compatto, ma non modificabile. La forma char[3][16] riserva 48 byte di memoria tua, quindi ogni riga si può riscrivere.
Errori comuni
- Dimenticare il byte del terminatore.
char s[5] = "hello"non ha spazio per'\0'. Dimensiona perstrlen + 1. - Usare
sizeofquando intendevistrlen.sizeofsu un puntatore dà la dimensione del puntatore, non la lunghezza del testo. - Scrivere tramite un
char *che punta a un letterale. Dichiara questi puntatori comeconst char *. - Confrontare con
==. Confronta gli indirizzi. Usastrcmp. - Leggere con
scanf("%s", buf). Non ha limiti di dimensione e va allegramente in overflow; preferiscifgets.
Domande frequenti
Come si dichiara una stringa in C?
Come array di char: char name[] = "Ada";. Il C non ha un tipo stringa integrato: quell'array contiene 'A', 'd', 'a' e un quarto byte nascosto, '\0', che segna la fine. Se vuoi spazio per modificare il testo in seguito, dai all'array una dimensione esplicita: char name[32] = "Ada";.
Cos'è il terminatore nullo in C?
Il byte '\0' (valore zero) che segna dove finisce una stringa. Ogni funzione standard, come printf("%s"), strlen o strcpy, legge in avanti finché non trova quel byte. Un array di char senza terminatore non è una stringa, e passarlo a quelle funzioni le fa leggere oltre la fine della tua memoria.
Perché non posso assegnare una stringa con = in C?
Perché un array non è un valore che puoi copiare con =. char a[10]; a = "hi"; non compila. Copia invece i caratteri con strcpy(a, "hi") di <string.h>, oppure inizializza nella dichiarazione, char a[10] = "hi";, che è l'unico punto in cui la scorciatoia funziona.
Qual è la differenza tra char * e char [] in C?
char s[] = "hi" crea un array modificabile con una copia privata del testo. char *p = "hi" crea un puntatore a un letterale stringa, che vive in memoria di sola lettura: leggerlo va bene, scriverci attraverso manda in crash il programma. Usa l'array quando intendi modificare, il puntatore quando devi solo leggere.