Determinare la lunghezza di una stringa in linguaggio C

In linguaggio C le stringhe sono array di caratteri terminati dal carattere speciale \0. Per determinare la lunghezza di una stringa, cioè il numero di caratteri che la compongono, non possiamo usare l'operatore sizeof, ma dobbiamo usare delle funzioni di libreria apposite definite in string.h.

Tali funzioni sono strlen, strnlen e strnlen_s. La funzione strlen restituisce la lunghezza di una stringa, strnlen restituisce la lunghezza di una stringa fino ad un limite superiore e strnlen_s è una versione sicura di strnlen introdotta nello standard C11.

In questa lezione studieremo queste funzioni nel dettaglio e ne analizzeremo una possibile implementazione.

Funzione strlen - Lunghezza di una stringa

Sappiamo che una stringa in linguaggio C è una sequenza di caratteri che termina con il carattere terminatore \0.

In C non possiamo usare l'operatore sizeof per determinare la lunghezza di una stringa per due motivi:

  • Se applicato ad un array di caratteri, sizeof restituisce la dimensione dell'array, non la lunghezza della stringa:

    char s[128] = "Hello";
    
    printf("%zu\n", sizeof(s)); // stampa "128"
    

    In questo caso sizeof restituisce la dimensione dell'array s, che è 128 byte, ma la stringa s contiene solo 5 caratteri più il terminatore \0.

  • Se applicato ad un puntatore, sizeof restituisce la dimensione del puntatore, non la lunghezza della stringa:

    char *s = "Hello";
    
    printf("%zu\n", sizeof(s)); // stampa "8" su un sistema a 64 bit
    

    In questo caso sizeof restituisce la dimensione del puntatore s, che è 8 byte su un sistema a 64 bit.

Per determinare la lunghezza di una stringa, dobbiamo usare la funzione strlen della libreria standard string.h.

La funzione strlen accetta un puntatore a una stringa e restituisce il numero di caratteri della stringa, escluso il terminatore \0. La sua firma è:

size_t strlen(const char *s);

Vediamo qualche esempio:

#include <stdio.h>
#include <string.h>

int main() {
    char s[128] = "Hello";
    char *t = "World";
    char *v = "";

    printf("La lunghezza di s è %zu\n", strlen(s)); // stampa "5"
    printf("La lunghezza di t è %zu\n", strlen(t)); // stampa "5"
    printf("La lunghezza di v è %zu\n", strlen(v)); // stampa "0"

    return 0;
}

Questo esempio ci consente di effettuare alcune importanti osservazioni:

  • Nel primo caso, strlen(s), la funzione restituisce 5, che è la lunghezza della stringa s escluso il terminatore \0. Non restituisce 128, che è la dimensione dell'array s.
  • Nel secondo caso, strlen(t), la funzione restituisce 5, che è sempre la lunghezza della stringa t escluso il terminatore \0. Non restituisce 8, che è la dimensione del puntatore t.
  • Nell'ultimo caso, abbiamo invocato strlen su una stringa vuota, v, che è composta esclusivamente dal terminatore \0. La funzione restituisce 0, dato che la stringa è composta dal singolo terminatore \0.
Definizione

Funzione strlen

La funzione strlen accetta un puntatore a una stringa e restituisce il numero di caratteri della stringa, escluso il terminatore \0.

Essa è definita nel file di intestazione string.h:

#include <string.h>

La sua firma è:

size_t strlen(const char *s);
Nota

La funzione strlen non è sicura

La funzione strlen ha due gravi problemi:

  1. Non verifica se il puntatore passato è valido. Se il puntatore non punta ad una stringa valida, il comportamento è indefinito.
  2. Non verifica se la stringa è effettivamente terminata con il carattere \0. Se la stringa non è terminata correttamente, il comportamento è indefinito.

Funzione strnlen - Lunghezza di una stringa con limite

Per evitare il problema di strlen, possiamo usare la funzione strnlen. Questa funzione, in realtà, non è una funzione standard del linguaggio C. Ma molti compilatori, tra cui GCC, Clang e Microsoft Visual C++, la forniscono come estensione della libreria standard.

La funzione strnlen accetta un puntatore a una stringa e un limite superiore e restituisce il numero di caratteri della stringa, escluso il terminatore \0, fino al limite superiore. La sua firma è:

size_t strnlen(const char *s, size_t maxlen);

In particolare, se la stringa s è terminata prima di raggiungere maxlen, la funzione restituisce la lunghezza della stringa. Se la stringa non è terminata prima di raggiungere maxlen, la funzione restituisce maxlen.

Vediamo un esempio:

#include <stdio.h>
#include <string.h>

int main() {
    char s[128] = "Hello, World!";

    /* Stampa 13 */
    printf("La lunghezza di s è %zu\n", strnlen(s, 128));

    /* Stampa 5 */
    printf("La lunghezza di s è %zu\n", strnlen(s, 5));

    return 0;
}

In questo esempio, la stringa s è "Hello, World!", che ha una lunghezza di 13 caratteri. Tuttavia, quando invochiamo strnlen con limiti differenti, otteniamo risultati diversi:

  • Quando invochiamo strnlen(s, 128), la funzione restituisce 13, che è la lunghezza della stringa s escluso il terminatore \0.
  • Quando invochiamo strnlen(s, 5), la funzione restituisce 5, che è il limite superiore passato come argomento.
Definizione

Funzione strnlen

La funzione strnlen accetta un puntatore a una stringa e un limite superiore e restituisce il numero di caratteri della stringa, escluso il terminatore \0, fino al limite superiore.

Essa, tipicamente, è definita come estensione della libreria standard in molti compilatori, tra cui GCC, Clang e Microsoft Visual C++ ed è definita nel file di intestazione string.h:

#include <string.h>

La sua firma è:

size_t strnlen(const char *s, size_t maxlen);
Nota

La funzione strnlen non è standard

La funzione strnlen non è definita nello standard C, ma è una estensione di alcune librerie standard come GNU libc e Microsoft Visual C++.

Funzione strnlen_s - Lunghezza di una stringa con limite e controllo di errore

A partire dallo standard C11, è stata introdotta la funzione strnlen_s che è una versione sicura di strnlen. La funzione strnlen_s accetta un puntatore a una stringa e il limite superiore e restituisce il numero di caratteri della stringa, escluso il terminatore \0, fino al limite superiore. In questo è simile in tutto e per tutto a strnlen.

Tuttavia, strnlen_s controlla anche se il puntatore passato è valido. Se il puntatore non punta ad una stringa valida restituisce 0.

La sua firma è:

size_t strnlen_s(const char *s, size_t maxlen);
Definizione

Funzione strnlen_s

La funzione strnlen_s, definita nello standard C11, accetta un puntatore a una stringa e un limite superiore e restituisce il numero di caratteri della stringa, escluso il terminatore \0, fino al limite superiore. Se la stringa non è valida, restituisce 0.

Essa è definita nel file di intestazione string.h:

#include <string.h>

La sua firma è:

size_t strnlen_s(const char *s, size_t maxlen);

Vediamo un esempio:

#include <stdio.h>
#include <string.h>

int main() {
    char s[128] = "Hello, World!";

    /* Stampa 13 */
    printf("La lunghezza di s è %zu\n", strnlen_s(s, 128));

    /* Stampa 5 */
    printf("La lunghezza di s è %zu\n", strnlen_s(s, 5));

    /* Stampa 0 */
    printf("La lunghezza di s è %zu\n", strnlen_s(NULL, 128));

    return 0;
}

Come si vede da questo esempio, la funzione strnlen_s si comporta come strnlen. Tuttavia, se il puntatore passato non è valido, la funzione restituisce 0 come si vede nell'ultimo caso.

Implementazione di strlen, strnlen e strnlen_s

A scopo didattico, è interessante vedere come possiamo implementare le funzioni strlen, strnlen e strnlen_s in linguaggio C. Ciò ci consentirà di vedere, inoltre, quali siano i problemi di sicurezza che queste funzioni hanno.

Partiamo da una semplice implementazione di strlen che chiameremo mia_strlen:

1
2
3
4
5
6
7
8
9
size_t mia_strlen(const char *s) {
    const char *p = s;

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

    return p - s;
}

Analizziamo il codice:

  • Riga 1: Definiamo la funzione mia_strlen che accetta un puntatore a una stringa s e restituisce la lunghezza della stringa;
  • Riga 2: Definiamo un puntatore p e lo inizializziamo con il puntatore s, ossia con la stringa passata come argomento;
  • Riga 4-6: Iteriamo finché non raggiungiamo il terminatore \0. Ad ogni iterazione incrementiamo il puntatore p;
  • Riga 8: Restituiamo la differenza tra il puntatore p e il puntatore s, che è la lunghezza della stringa.

In particolare, quest'ultimo punto è interessante: la differenza tra due puntatori è il numero di elementi tra i due puntatori. In questo caso, la differenza tra p e s è il numero di caratteri tra p e s, ossia la lunghezza della stringa.

Nel codice di sopra, possiamo riscrivere il ciclo alle righe 4-6 in un modo più conciso:

while (*p++);

Questo ciclo incrementa il puntatore p e valuta il valore puntato da p finché non raggiunge il terminatore \0 che, essendo pari al codice ASCII 0, è valutato come falso e quindi chiude il ciclo.

Se osserviamo bene il codice, possiamo capire i problemi che questa funzione presenta:

  1. Non controlla se il puntatore s è valido. Se s è NULL, il comportamento è indefinito;
  2. Non controlla se la stringa è effettivamente terminata con il carattere \0. Se la stringa non è terminata correttamente, il comportamento è indefinito. Infatti in questo caso, il ciclo while alla riga 4 potrebbe andare avanti all'infinito.

La funzione strnlen, invece, accetta un limite superiore e risolve il secondo problema.

Vediamo una possibile implementazione di strnlen che chiameremo mia_strnlen:

1
2
3
4
5
6
7
8
9
size_t mia_strnlen(const char *s, size_t maxlen) {
    const char *p = s;

    while (*p != '\0' && p - s < maxlen) {
        p++;
    }

    return p - s;
}

L'implementazione è molto simile a quella di strlen, con una differenza: dobbiamo controllare che non superiamo il limite maxlen. Per fare ciò, aggiungiamo una condizione al ciclo while, riga 4, che controlla sia il terminatore \0 che la differenza tra p e s.

In questo modo, la funzione mia_strnlen restituisce la lunghezza della stringa fino al limite maxlen.

Questa funzione è, certamente, più sicura di strlen, ma presenta ancora un problema: non controlla se il puntatore s è valido. Se s è NULL, il comportamento è indefinito.

La funzione strnlen_s, infine, risolve anche il primo problema, ossia controlla se il puntatore s è valido.

Proviamo, infine, a implementare la funzione strnlen_s. Realizziamone una versione che chiameremo mia_strnlen_s:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
size_t mia_strnlen_s(const char *s, size_t maxlen) {
    if (s == NULL) {
        return 0;
    }

    const char *p = s;

    while (*p != '\0' && p - s < maxlen) {
        p++;
    }

    return p - s;
}

In questo caso, la funzione mia_strnlen_s è identica a mia_strnlen, con l'aggiunta di una condizione iniziale, righe 2-4, che controlla se il puntatore s è valido. Se il puntatore s è NULL, la funzione restituisce 0.

In questo modo, la funzione mia_strnlen_s è sicura sia rispetto al puntatore s che al limite maxlen.

In Sintesi

In questa lezione abbiamo visto come determinare la lunghezza di una stringa in linguaggio C. Abbiamo visto che non possiamo usare l'operatore sizeof per determinare la lunghezza di una stringa e che dobbiamo usare la funzione strlen della libreria standard string.h.

Abbiamo visto che la funzione strlen non è sicura, in quanto non controlla se il puntatore passato è valido e se la stringa è effettivamente terminata con il carattere \0.

Per evitare questi problemi, abbiamo visto la funzione strnlen, che accetta un limite superiore e restituisce la lunghezza della stringa fino al limite superiore. Inoltre, abbiamo visto la funzione strnlen_s, introdotta nello standard C11, che controlla anche se il puntatore passato è valido.

Infine, abbiamo visto come implementare le funzioni strlen, strnlen e strnlen_s in linguaggio C e perché le prime due funzioni non sono sicure.