Copiare Stringhe in Linguaggio C
In linguaggio C non si può usare l'operatore di assegnamento =
per copiare il contenuto di una stringa in un'altra stringa.
Per questo scopo, nella libreria standard del C sono state introdotte due funzioni: strcpy
e strncpy
.
Queste due funzioni sono definite nel file di intestazione string.h
e permettono di copiare una stringa in un'altra stringa. In questa lezione vedremo come funzionano queste due funzioni e come possiamo utilizzarle in modo sicuro.
Funzione strcpy
- Copia di una stringa
La funzione strcpy
copia una stringa in un'altra stringa. La stringa di destinazione deve essere abbastanza grande per contenere la stringa di origine.
Per poterla utilizzare è necessario includere il file di intestazione string.h
:
#include <string.h>
Il suo prototipo è il seguente:
char *strcpy(char *dest, const char *src);
La funzione copia il contenuto di src
in dest
e restituisce dest
. In particolare, la funzione copia tutti i caratteri di src
in dest
, compreso il carattere di terminazione '\0'
. Per tal motivo bisogna assicurarsi che la stringa di destinazione abbia abbastanza spazio per contenere la stringa di origine. La funzione restituisce un puntatore alla stringa di destinazione, ossia dest
.
In particolare, se la stringa di partenza è composta da n
caratteri, la stringa di destinazione deve avere almeno n + 1
caratteri per contenere la stringa di partenza e il carattere di terminazione '\0'
.
L'esistenza di questa funzione nella libreria standard sopperisce al fatto che in linguaggio C non esiste un operatore di assegnamento per le stringhe. In particolare, l'assegnamento di una stringa ad un'altra stringa non copia il contenuto della stringa di origine in quella di destinazione, ma copia il puntatore alla stringa di origine in quello della stringa di destinazione. Questo significa che se si assegna una stringa ad un'altra stringa, e poi si modifica la stringa di origine, la stringa di destinazione verrà modificata anche essa.
char s1[] = "Hello";
char s2[] = "World";
s2 = s1; // s2 punta alla stessa stringa di s1
s1[0] = 'J'; // s2[0] diventa 'J'
printf("%s %s\n", s1, s2); // stampa "Jello Jello"
Inoltre, un'espressione di inizializzazione del genere è sbagliata:
char s1[128];
s1 = "Hello"; // ERRORE
L'operatore di assegnamento =
non può essere utilizzato per copiare stringhe
Dal momento che le stringhe in linguaggio C sono, in essenza, puntatori ad array di caratteri, l'operatore di assegnamento =
non può essere utilizzato per copiare il contenuto di una stringa in un'altra stringa. Invece, l'operatore di assegnamento =
copia il puntatore alla stringa di origine in quello della stringa di destinazione.
Il codice che segue è errato:
/* ERRORE */
char s1[128];
s1 = "Hello";
Per tal motivo, in entrambe i casi è necessario utilizzare la funzione strcpy
per copiare una stringa in un'altra stringa.
char s1[] = "Hello";
char s2[] = "World";
strcpy(s2, s1); // copia la stringa di s1 in s2
s1[0] = 'J'; // s2[0] rimane 'H'
printf("%s %s\n", s1, s2); // stampa "Jello Hello"
Da notare che, nell'esempio di sopra, s2
e s1
sono due array con lo stesso numero di caratteri. Per questo, copiare la stringa s1
in s2
non causa problemi.
char s1[128];
strcpy(s1, "Hello"); // copia la stringa "Hello" in s1
Nella maggior parte dei casi, il risultato di strcpy
viene scartato. Vi sono casi in cui, invece, risulta utile utilizzare tale risultato. Ad esempio, supponiamo di voler inizializzare due stringhe con una stessa stringa letterale. Possiamo concatenare due invocazioni della funzione strcpy
come segue:
char s1[128];
char s2[128];
strcpy(s2, strcpy(s1, "Hello")); // copia la stringa "Hello" in s1 e in s2
Funzione strcpy
La funzione strcpy
copia una stringa in un'altra stringa.
Essa è definita nel file di intestazione string.h
:
#include <string.h>
La sua firma è la seguente:
char *strcpy(char *dest, const char *src);
dest
è un puntatore alla stringa di destinazione;src
è un puntatore alla stringa di origine.
La funzione copia il contenuto di src
in dest
e restituisce dest
. In particolare, la funzione copia tutti i caratteri di src
in dest
, compreso il carattere di terminazione '\0'
.
Quando si usa la funzione strcpy
, bisogna assicurarsi che:
src
punti ad una locazione valida;dest
abbia abbastanza spazio per contenere la stringa di origine.
La funzione strcpy
non è sicura
La funzione strcpy
non controlla se la stringa di destinazione è abbastanza grande per contenere la stringa di origine. Se la stringa di destinazione non è abbastanza grande, la funzione strcpy
potrebbe scrivere oltre i limiti della stringa di destinazione, causando un errore di segmentazione, o segmentation fault. Per questo motivo, è necessario assicurarsi che la stringa di destinazione abbia abbastanza spazio per contenere la stringa di origine.
Per evitare questi problemi nello standard C è stata introdotta la funzione strncpy
. Nello scrivere i programmi è consigliabile utilizzare sempre la funzione strncpy
invece di strcpy
.
Funzione strncpy
- Copia di una stringa con controllo
La funzione strncpy
copia una stringa in un'altra stringa, quindi è simile alla funzione strcpy
. La differenza è che la funzione strncpy
accetta un parametro in più, ossia il numero massimo di caratteri da copiare.
Il suo prototipo è il seguente:
char *strncpy(char *dest, const char *src, size_t n);
La funzione copia il contenuto di src
in dest
e restituisce dest
. In particolare, la funzione copia al più n
caratteri di src
in dest
. La funzione restituisce un puntatore alla stringa di destinazione, ossia dest
.
Ad esempio:
#define MAX_LEN 128
char s1[] = "Ciao Mondo!";
char s2[MAX_LEN];
/* Copia al più 128 caratteri di s1 in s2 */
strncpy(s2, s1, MAX_LEN);
In questo esempio, fintanto che la stringa s2
sia abbastanza grande per contenere la stringa s1
, la funzione strncpy
copierà la stringa s1
in s2
. Se la stringa s2
non è abbastanza grande per contenere la stringa s1
, la funzione strncpy
copierà solo i primi n
caratteri di s1
in s2
. In questo caso, al massimo verranno copiati 128 caratteri (incluso il terminatore di stringa).
Per tal motivo, anche la funzione strncpy
nasconde un problema di sicurezza. Se la stringa di destinazione non è abbastanza grande per contenere la stringa di origine, la funzione strncpy
non copia il carattere di terminazione '\0'
. Un modo più sicuro di invocarla è il seguente:
#define MAX_LEN 128
char s1[] = "Ciao Mondo!";
char s2[MAX_LEN];
/* Copia al più 127 caratteri di s1 in s2 */
strncpy(s2, s1, MAX_LEN - 1);
/* Aggiunge il terminatore di stringa */
s2[MAX_LEN - 1] = '\0';
In questo modo ci assicuriamo che vi sia almeno uno spazio per il carattere di terminazione '\0'
che però dovremo inserire manualmente. Usando questa tecnica saremo sempre sicuri che la stringa s2
sia terminata correttamente.
Funzione strncpy
La funzione strncpy
copia una stringa in un'altra stringa, limitando il numero di caratteri copiati.
Essa è definita nel file di intestazione string.h
:
#include <string.h>
La sua firma è la seguente:
char *strncpy(char *dest, const char *src, size_t n);
dest
è un puntatore alla stringa di destinazione;src
è un puntatore alla stringa di origine;n
è il numero massimo di caratteri da copiare.
La funzione copia al più n
caratteri di src
in dest
e restituisce dest
. Se la stringa di origine è più corta di n
, la funzione copia tutti i caratteri di src
in dest
, incluso il carattere di terminazione '\0'
. Se la stringa di origine è più lunga di n
, la funzione copia solo i primi n
caratteri di src
in dest
.
Quando si usa la funzione strncpy
, bisogna assicurarsi che:
src
punti ad una locazione valida;dest
abbia abbastanza spazio per contenere almenon
caratteri, incluso il carattere di terminazione'\0'
.
La funzione strncpy
non è sicura
La funzione strncpy
non copia il carattere di terminazione '\0'
se la stringa di destinazione non è abbastanza grande per contenere la stringa di origine. Per questo motivo, è necessario assicurarsi che la stringa di destinazione abbia abbastanza spazio per contenere almeno n
caratteri, incluso il carattere di terminazione '\0'
.
Utilizzo di strncpy
Un modo sicuro di utilizzare la funzione strncpy
è il seguente:
- Supponendo che la stringa di destinazione possa contenere al massimo
n
caratteri (incluso il terminatore'\0'
), invocare la funzionestrncpy
conn - 1
come terzo argomento; - Dopo aver invocato
strncpy
, aggiungere manualmente il terminatore'\0
alla fine della stringa di destinazione.
#define MAX_LEN 128
char destinazione[MAX_LEN];
strncpy(destinazione, sorgente, MAX_LEN - 1);
destinazione[MAX_LEN - 1] = '\0';
Implementazione di strcpy
e strncpy
Sebbene siano due funzioni di libreria, è molto istruttivo implementare strcpy
e strncpy
da zero. Implementarle da zero ci permette di capire meglio come funzionano le stringhe in linguaggio C. Inoltre ci permette di capire perché queste funzioni sono vulnerabili e come possiamo proteggerci da questi problemi.
Partiamo da una possibile implementazione di strcpy
che chiameremo mia_strcpy
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Analizziamo il codice:
- Riga 2: Inizializziamo un puntatore
p
alla stringa di destinazione. Questo ci permetterà di restituire la stringa di destinazione alla fine della funzione. -
Riga 4-8: Copiamo i caratteri della stringa di origine nella stringa di destinazione fino a quando non raggiungiamo il carattere di terminazione
'\0'
. Per questo usiamo un ciclowhile
che scorre i caratteri disorgente
e li copia indestinazione
.Da notare che, alle righe 6 e 7, incrementiamo i puntatori
destinazione
esorgente
per passare al carattere successivo. -
Riga 10: Aggiungiamo il carattere di terminazione
'\0'
alla fine della stringa di destinazione. - Riga 12: Restituiamo la stringa di destinazione.
Questa è, più o meno, l'implementazione di strcpy
nella libreria standard C. Se la osserviamo bene, possiamo notare quali sono i problemi di sicurezza di questa funzione:
-
Il primo problema consiste nel fatto che la funzione non controlla la validità dei puntatori. In particolare, non controlla se
sorgente
edestinazione
puntano a locazioni di memoria valide. Se uno dei due puntatori non è valido, il comportamento della funzione è indefinito. -
Il secondo problema è che la funzione non controlla se la stringa di destinazione ha abbastanza spazio per contenere la stringa di origine. Del resto, non avrebbe modo di farlo. In C, purtroppo, le stringhe non contengono, di base, informazioni riguardanti la loro lunghezza. Per questo motivo, se la stringa di destinazione non è abbastanza grande per contenere la stringa di origine, la funzione
strcpy
scriverà oltre i limiti della stringa di destinazione, causando un errore di segmentazione, o segmentation fault.In particolare, supponiamo che
destinazione
sia una stringa che può contenere al massimo 10 caratteri. Sesorgente
è una stringa di 17 caratteri, ad esempio"Ciao, come si va?"
, accade questo:-
Inizialmente, supponendo che
destinazione
contenga solo caratteri nulli, la stringa di destinazione è la seguente:+----+----+----+----+----+----+----+----+----+----+ | \0 | \0 | \0 | \0 | \0 | \0 | \0 | \0 | \0 | \0 | +----+----+----+----+----+----+----+----+----+----+ ^ | p
Il puntatore
p
punta alla prima locazione. -
La funzione inizia a copiare i primi caratteri di
sorgente
indestinazione
. Dopo aver copiato i primi 10 caratteri, la stringa di destinazione è la seguente:+----+----+----+----+----+----+----+----+----+----+ | C | i | a | o | , | | c | o | m | e | +----+----+----+----+----+----+----+----+----+----+ ^ | p
Il puntatore
p
punta al decimo carattere didestinazione
. -
Ma, a questo punto, dato che la stringa
sorgente
non è terminata, la funzione continua a copiare i caratteri disorgente
nelle locazioni di memoria adiacenti:+----+----+----+----+----+----+----+----+----+----+ + + + | C | i | a | o | , | | c | o | m | e | | s | i | +----+----+----+----+----+----+----+----+----+----+ + + + ^ | p
-
Queste locazioni, tuttavia, non appartengono alla stringa di destinazione. Cosa accade in questi casi non è predicibile e può causare comportamenti indefiniti. Nel peggiore dei casi, il programma potrebbe terminare con un errore di segmentazione.
-
La funzione strncpy
è stata introdotta nella libreria standard del C per risolvere il secondo problema. Vediamo un'implementazione di strncpy
che chiameremo mia_strncpy
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Analizziamo il codice:
- Riga 2: Inizializziamo un puntatore
p
alla stringa di destinazione. Questo ci permetterà di restituire la stringa di destinazione alla fine della funzione. -
Riga 4-9: Copiamo al massimo
n
caratteri della stringa di origine nella stringa di destinazione fino a quando non raggiungiamo il carattere di terminazione'\0'
. Per fare ciò, usiamo un ciclowhile
che scorre i caratteri disorgente
e li copia indestinazione
. Il ciclo termina quando abbiamo copiaton
caratteri o quando raggiungiamo il carattere di terminazione'\0'
.-
Da notare che, alle righe 6,7 e 8, incrementiamo i puntatori
destinazione
esorgente
per passare al carattere successivo e decrementiamon
per tener traccia del numero di caratteri copiati. -
La condizione del
while
, infatti, controlla che ci siano ancora caratteri da copiare (n > 0
) e che non abbiamo raggiunto il carattere di terminazione'\0'
(*sorgente != '\0'
).
-
-
Riga 11-15: Arrivati a questo punto possono essersi verificate due situazioni:
-
Abbiamo copiato tutti i caratteri di
sorgente
indestinazione
, ma la stringa di origine non è terminata. In questo caso, dobbiamo aggiungere il carattere di terminazione'\0'
alla fine della stringa di destinazione. Per fare ciò, usiamo un secondo ciclowhile
che aggiunge il carattere di terminazione'\0'
un numero di volte pari ai caratteri rimanenti da copiare (n
). -
Abbiamo copiato
n
caratteri disorgente
indestinazione
, ma la stringa di origine non è terminata. In questo caso il secondo ciclowhile
non viene eseguito.
-
-
Riga 17: Restituiamo la stringa di destinazione.
Questa è anch'essa, approssimativamente, l'implementazione di strncpy
nella libreria standard C. Sebbene questa funzione migliori la sicurezza rispetto a strcpy
, essa non è immune da problemi. Vediamo quali sono:
-
Anche la
strncpy
non controlla la validità dei puntatori. Se uno dei due puntatori non è valido, il comportamento della funzione è indefinito. -
Non controlla la validità del parametro
n
. Sen
è maggiore della lunghezza effettiva didestinazione
, anche in questo caso la funzione andrà a scrivere oltre i limiti della stringa di destinazione.
In Sintesi
In linguaggio C non possiamo usare l'operatore di assegnamento, =
, per copiare il contenuto di una stringa in un'altra stringa. Questo perché, in C, le stringhe sono puntatori ad array di caratteri. Per cui, l'operatore di assegnamento =
copia il puntatore alla stringa di origine in quello della stringa di destinazione. Per copiare il contenuto di una stringa in un'altra stringa, dobbiamo utilizzare le funzioni di libreria strcpy
o strncpy
.
- La funzione
strcpy
copia una stringa in un'altra stringa. La stringa di destinazione deve essere abbastanza grande per contenere la stringa di origine. - La funzione
strncpy
copia una stringa in un'altra stringa, limitando il numero di caratteri copiati.
Nella prossima lezione vedremo un'altra importante funzione della libreria standard C per la manipolazione delle stringhe: strlen
.