Allocazione Dinamica delle Stringhe in Linguaggio C

Applichiamo l'allocazione dinamica della memoria per lavorare con le stringhe in linguaggio C.

Le stringhe si prestano bene ad essere allocate dinamicamente in quanto non sempre è possibile prevedere in anticipo la loro lunghezza. In questa lezione, vedremo come allocare dinamicamente una stringa, come inizializzarla, come deallocarla e come realizzare delle funzioni che restituiscono stringhe allocate dinamicamente.

Stringhe e Allocazione Dinamica

L'allocazione dinamica della memoria è utilissima quando nei programmi scritti in C dobbiamo lavorare con le stringhe.

Le stringhe, infatti, in linguaggio C non sono altro che array di caratteri char. Tuttavia, non è semplice anticipare in fase di realizzazione di un programma quanto questi array debbano essere lunghi. Se scegliamo di usare array allocati staticamente, dobbiamo prevedere una lunghezza massima possibile. Tuttavia, questa non è la scelta ottima. In primo luogo, perché scegliendo una lunghezza troppo grande sprechiamo memoria, in secondo luogo perché potrebbe verificarsi il caso in cui la stringa sia più lunga di quanto previsto, causando un buffer overflow.

Allocando le stringhe in maniera dinamica stiamo postponendo la scelta della lunghezza dell'array, e possiamo decidere la lunghezza della stringa in fase di esecuzione del programma in base alle necessità.

Utilizzo della funzione malloc per allocare una stringa

Nella lezione precedente, abbiamo visto che la funzione malloc ci consente di allocare in maniera dinamica un blocco o area di memoria.

Il suo prototipo è il seguente:

#include <stdlib.h>

void *malloc(size_t size);

La funzione prende in ingresso una dimensione specificata dal parametro size che è espresso in byte. Essa restituisce un puntatore void * al blocco di memoria allocato. Nel caso la funzione non riesca ad allocare la memoria richiesta, restituirà il valore NULL.

Utilizzare malloc per allocare una stringa è molto semplice. Infatti, lo standard del linguaggio C impone che la dimensione di un char sia esattamente un byte. In altre parole:

1 == sizeof(char)

Pertanto, se vogliamo allocare una stringa di n caratteri, dobbiamo scrivere il seguente codice:

char *stringa = NULL;

stringa = (char *) malloc(n + 1);

Da notare che, nell'invocare malloc, abbiamo passato come argomento il valore n + 1 anziché n, in quanto dobbiamo allocare un byte in più per il carattere terminatore della stringa, ovvero il carattere '\0'.

Nel codice di sopra abbiamo inserito un cast esplicito al tipo char *. In realtà, lo standard C non richiede un cast esplicito, ma è una pratica comune per evitare warning del compilatore. Inoltre, in C++ il cast è obbligatorio, per cui se dal C si passa al C++ è bene abituarsi a questa pratica.

Un'altra buona pratica, come abbiamo già detto nella lezione precedente, è quella di controllare sempre se malloc ha restituito NULL. Se malloc restituisce NULL, significa che non è riuscita ad allocare la memoria richiesta. In tal caso, è buona norma terminare il programma o gestire l'errore in maniera opportuna.

if (stringa == NULL) {
    printf("Errore: impossibile allocare la memoria richiesta\n");
    exit(EXIT_FAILURE);
}

Ricapitolando:

Definizione

Procedimento per allocare una stringa in modo dinamico in linguaggio C

Per allocare una stringa in modo dinamico in linguaggio C, il codice tipico da adoperare è il seguente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdlib.h>

/* ... */

char *stringa = NULL;

stringa = (char *) malloc(n + 1);

if (stringa == NULL) {
    printf("Errore: impossibile allocare la memoria richiesta\n");
    exit(EXIT_FAILURE);
}

Dove:

  • Bisogna includere l'header file stdlib.h per poter utilizzare la funzione malloc;
  • stringa è un puntatore a carattere che punta all'area di memoria allocata;
  • n è la dimensione della stringa da allocare;
  • Alla riga 5 si invoca la malloc passando come argomento n + 1 per allocare un byte in più per il carattere terminatore della stringa;
  • Il valore di ritorno della malloc viene convertito tramite un cast esplicito al tipo char * e assegnato a stringa;
  • Alla riga 7 si controlla se malloc ha restituito NULL. In tal caso, si stampa un messaggio di errore e si termina il programma (oppure si gestisce l'errore in maniera opportuna).

Una volta allocata la stringa, la si può adoperare come qualsiasi altra stringa in C. Ad esempio, si può copiare una stringa in un'altra, si può concatenare una stringa a un'altra, si può stampare la stringa a video, ecc.

Ovviamente, quando non se ne ha più bisogno, è buona norma deallocare la memoria utilizzata. Per deallocare la memoria di una stringa allocata dinamicamente, si può utilizzare la funzione free.

In tal caso la deallocazione è molto semplice. Basta invocare la funzione free sul puntatore alla stringa:

free(stringa);
Definizione

Deallocare una stringa allocata dinamicamente in linguaggio C

Per deallocare una stringa allocata dinamicamente, basta invocare la funzione free passando come argomento il puntatore alla stringa:

free(stringa);

Inizializzazione di una stringa allocata dinamicamente

La funzione malloc non inizializza la memoria che alloca. Pertanto, quando allochiamo dinamicamente una stringa, il contenuto della stringa è indefinito. In altre parole, non possiamo sapere cosa c'è all'interno della stringa.

Tornando al codice di sopra, il puntatore stringa punterà ad un array non inizializzato di n + 1 caratteri:

Situazione iniziale della stringa allocata dinamicamente
Figura 1: Situazione iniziale della stringa allocata dinamicamente

Un modo semplice di inizializzare una stringa allocata dinamicamente è quello di adoperare la funzione strcpy per copiare una stringa letterale al suo interno.

Ad esempio, volendo inizializzare la stringa con la parola "ciao", possiamo scrivere:

#include <string.h>

/* ... */

strcpy(stringa, "ciao");

Adesso, i primi cinque caratteri dell'array saranno:

Stringa allocata dinamicamente dopo essere stata inizializzata
Figura 2: Stringa allocata dinamicamente dopo essere stata inizializzata
Definizione

Inizializzare una stringa allocata dinamicamente in linguaggio C

Per inizializzare una stringa allocata dinamicamente, si può adoperare la funzione strcpy per copiare una stringa letterale al suo interno:

#include <string.h>

/* ... */

strcpy(stringa, "stringa di inizializzazione");

Esempio di Allocazione Dinamica di una Stringa

Di seguito, mostriamo un esempio completo di allocazione dinamica di una stringa in linguaggio C:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int leggi_riga(char *stringa, int lunghezza);

int main() {
    char *stringa = NULL;
    int n = 0;

    printf("Inserisci la lunghezza della stringa: ");
    scanf("%d", &n);

    stringa = (char *) malloc(n + 1);

    if (stringa == NULL) {
        printf("Errore: impossibile allocare la memoria richiesta\n");
        exit(EXIT_FAILURE);
    }

    /* Richiede all'utente di inserire la stringa */
    printf("Inserisci la stringa: ");
    leggi_riga(stringa, n + 1);

    printf("La stringa allocata dinamicamente è: %s\n", stringa);

    free(stringa);

    return 0;
}

int leggi_riga(char *stringa, int lunghezza) {
    int c;
    int i = 0;

    while ((c = getchar()) != '\n' &&
           (c != EOF) &&
           (i < lunghezza - 1)) {
        stringa[i++] = c;
    }

    stringa[i] = '\0';

    return i;
}

Il programma chiede all'utente di inserire la lunghezza della stringa da allocare. Successivamente, alloca dinamicamente la stringa e richiede all'utente di inserire la stringa. Infine, stampa la stringa allocata dinamicamente e dealloca la memoria.

Per leggere la stringa, abbiamo definito una funzione leggi_riga molto simile a quella che abbiamo mostrato nella lezione sulla lettura di stringhe da console.

Funzioni e Stringhe allocate dinamicamente

Attraverso l'allocazione dinamica, possiamo realizzare delle funzioni che creano e restituiscono stringhe allocate dinamicamente. Ossia, funzioni che restituiscono nuove stringhe che non esistevano prima dell'invocazione della funzione stessa.

Proviamo, ad esempio, a realizzare una funzione che prende due stringhe in ingresso e restituisce una nuova stringa che è la concatenazione delle due.

La libreria standard del C fornisce la funzione strcat che abbiamo già visto, ma essa funziona in modo diverso: modifica la stringa di partenza aggiungendo la seconda stringa passata. Noi vogliamo realizzare una funzione che restituisca una nuova stringa.

Per fare ciò, dobbiamo allocare dinamicamente una nuova stringa, copiare le due stringhe in essa e restituire il puntatore alla nuova stringa.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdlib.h>
#include <string.h>

/* ... */

char *concatena_stringhe(const char *s1, const char *s2) {
    /* Calcola la lunghezza della prima stringa */
    int n1 = strlen(s1);

    /* Calcola la lunghezza della seconda stringa */
    int n2 = strlen(s2);

    /* Alloca dinamicamente una nuova stringa */
    char *nuova_stringa = (char *) malloc(n1 + n2 + 1);

    /* Controlla se la malloc ha avuto successo */
    if (nuova_stringa == NULL) {
        /* In caso di errore, restituisce NULL */
        return NULL;
    }

    /* Copia la prima stringa nella nuova stringa */
    strcpy(nuova_stringa, s1);

    /* Concatena la seconda stringa alla nuova stringa */
    strcat(nuova_stringa, s2);

    /* Restituisce il puntatore alla nuova stringa */
    return nuova_stringa;
}

La funzione concatena_stringhe prende due stringhe in ingresso e procede in questo modo:

  1. Calcola la lunghezza della prima stringa s1 e la memorizza in n1, e la lunghezza della seconda stringa s2 e la memorizza in n2; per fare questo si affida alla funzione di libreria strlen;
  2. Alloca dinamicamente una nuova stringa nuova_stringa di dimensione n1 + n2 + 1 (perché dobbiamo allocare un byte in più per il carattere terminatore della stringa); per fare questo, usa la funzione malloc;
  3. Controlla se la malloc ha avuto successo; in caso contrario, restituisce NULL;
  4. Copia la prima stringa s1 nella nuova stringa nuova_stringa utilizzando la funzione strcpy;
  5. Concatena la seconda stringa s2 alla nuova stringa nuova_stringa utilizzando la funzione strcat;
  6. Restituisce il puntatore alla nuova stringa.

Di seguito, mostriamo un esempio di utilizzo della funzione concatena_stringhe:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

/* ... */

int main() {
    const char *s1 = "Hello, ";
    const char *s2 = "world!";

    char *s3 = concatena_stringhe(s1, s2);

    if (s3 == NULL) {
        printf("Errore: impossibile allocare la memoria richiesta\n");
        return 1;
    }

    printf("La stringa concatenata è: %s\n", s3);

    free(s3);

    return 0;
}

Il programma definisce due stringhe s1 e s2, e invoca la funzione concatena_stringhe.

Un dettaglio fondamentale è che la funzione concatena_stringhe restituisce un puntatore a una stringa allocata dinamicamente. Pertanto, è necessario deallocare la memoria utilizzata dalla stringa restituita invocando la funzione free.

Definizione

Stringhe allocate dinamicamente e restituite da funzioni

Quando una funzione restituisce una stringa allocata dinamicamente, è necessario deallocare la memoria utilizzata dalla stringa invocando la funzione free.

Conclusioni

In questa lezione abbiamo applicato l'allocazione dinamica della memoria per lavorare con le stringhe.

I concetti chiave che abbiamo imparato sono:

  • L'allocazione dinamica delle stringhe in C ci permette di posticipare la scelta della lunghezza della stringa;
  • Per allocare una stringa dinamicamente, dobbiamo utilizzare la funzione malloc e passare come argomento la dimensione della stringa più uno; questo perché dobbiamo allocare un byte in più per il carattere terminatore della stringa;
  • È buona norma controllare se malloc ha restituito NULL e gestire l'errore in maniera opportuna;
  • Per deallocare la memoria utilizzata da una stringa allocata dinamicamente, possiamo utilizzare la funzione free;
  • Per inizializzare una stringa allocata dinamicamente, possiamo utilizzare la funzione strcpy;
  • Possiamo realizzare delle funzioni che restituiscono stringhe allocate dinamicamente.

Inoltre, abbiamo mostrato un esempio completo di allocazione dinamica di una stringa e come realizzare una funzione che restituisce una nuova stringa che è la concatenazione di due stringhe.

Nella prossima lezione ci concentreremo sull'allocazione dinamica di array. Le stringhe sono array di caratteri, ma possiamo allocare dinamicamente anche array di altri tipi di dati.