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
Nota

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
Definizione

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.
Nota

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.

Definizione

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 almeno n caratteri, incluso il carattere di terminazione '\0'.
Nota

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'.

Consiglio

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 funzione strncpy con n - 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
char *mia_strcpy(char *destinazione, const char *sorgente) {
    char *p = destinazione;

    while (*sorgente != '\0') {
        *destinazione = *sorgente;
        destinazione++;
        sorgente++;
    }

    *destinazione = '\0';

    return p;
}

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 ciclo while che scorre i caratteri di sorgente e li copia in destinazione.

    Da notare che, alle righe 6 e 7, incrementiamo i puntatori destinazione e sorgente 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 e destinazione 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. Se sorgente è 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 in destinazione. 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 di destinazione.

    • Ma, a questo punto, dato che la stringa sorgente non è terminata, la funzione continua a copiare i caratteri di sorgente 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
char *mia_strncpy(char *destinazione, const char *sorgente, size_t n) {
    char *p = destinazione;

    while (n > 0 && *sorgente != '\0') {
        *destinazione = *sorgente;
        destinazione++;
        sorgente++;
        n--;
    }

    while (n > 0) {
        *destinazione = '\0';
        destinazione++;
        n--;
    }

    return p;
}

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 ciclo while che scorre i caratteri di sorgente e li copia in destinazione. Il ciclo termina quando abbiamo copiato n caratteri o quando raggiungiamo il carattere di terminazione '\0'.

    • Da notare che, alle righe 6,7 e 8, incrementiamo i puntatori destinazione e sorgente per passare al carattere successivo e decrementiamo n 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 in destinazione, 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 ciclo while che aggiunge il carattere di terminazione '\0' un numero di volte pari ai caratteri rimanenti da copiare (n).

    • Abbiamo copiato n caratteri di sorgente in destinazione, ma la stringa di origine non è terminata. In questo caso il secondo ciclo while 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. Se n è maggiore della lunghezza effettiva di destinazione, 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.