C Language Tokenizzazione: strtok (), strtok_r () e strtok_s ()


Esempio

La funzione strtok spezza una stringa in stringhe più piccole, o token, usando un set di delimitatori.

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

int main(void)
{
    int toknum = 0;
    char src[] = "Hello,, world!";
    const char delimiters[] = ", !";
    char *token = strtok(src, delimiters);
    while (token != NULL)
    {
        printf("%d: [%s]\n", ++toknum, token);
        token = strtok(NULL, delimiters);
    }
    /* source is now "Hello\0, world\0\0" */
}

Produzione:

1: [Hello]
2: [world]

La stringa di delimitatori può contenere uno o più delimitatori e diverse stringhe delimitatori possono essere utilizzate con ogni chiamata a strtok .

Le chiamate a strtok per continuare a tokenizzare la stessa stringa sorgente non dovrebbero passare di nuovo la stringa sorgente, ma passare NULL come primo argomento. Se viene passata la stessa stringa sorgente, il primo token verrà invece ridenominato. Cioè, dati gli stessi delimitatori, strtok restituirebbe semplicemente il primo token di nuovo.

Notare che come strtok non alloca nuova memoria per i token, modifica la stringa di origine . Cioè, nell'esempio precedente, la stringa src verrà manipolata per produrre i token a cui fa riferimento il puntatore restituito dalle chiamate a strtok . Ciò significa che la stringa di origine non può essere const (quindi non può essere una stringa letterale). Significa anche che l'identità del byte di delimitazione viene persa (cioè nell'esempio il "," e "!" Vengono effettivamente eliminati dalla stringa di origine e non è possibile stabilire quale carattere delimitatore corrisponde).

Si noti inoltre che più delimitatori consecutivi nella stringa di origine vengono considerati come uno; nell'esempio, la seconda virgola viene ignorata.

strtok non è né thread-safe né ri-entrant perché usa un buffer statico durante l'analisi. Ciò significa che se una funzione chiama strtok , nessuna funzione che chiama mentre sta usando strtok può anche usare strtok , e non può essere chiamata da alcuna funzione che sta usando strtok .

Un esempio che dimostra i problemi causati dal fatto che strtok non è rientrante è il seguente:

char src[] = "1.2,3.5,4.2";
char *first = strtok(src, ","); 

do 
{
    char *part;
    /* Nested calls to strtok do not work as desired */
    printf("[%s]\n", first);
    part = strtok(first, ".");
    while (part != NULL)
    {
        printf(" [%s]\n", part);
        part = strtok(NULL, ".");
    }
} while ((first = strtok(NULL, ",")) != NULL);

Produzione:

[1.2]
 [1]
 [2]

L'operazione prevista è che il ciclo do while esterno crei tre token costituiti da ogni stringa decimale ( "1.2" , "3.5" , "4.2" ), per ognuno dei quali lo strtok chiama il ciclo interno dovrebbe dividerlo in un separato stringhe di cifre ( "1" , "2" , "3" , "5" , "4" , "2" ).

Tuttavia, poiché strtok non è rientranti, ciò non si verifica. Invece il primo strtok crea correttamente il token "1.2 \ 0" e il ciclo interno crea correttamente i token "1" e "2" . Ma poi lo strtok nel ciclo esterno è alla fine della stringa usata dal ciclo interno e restituisce immediatamente NULL. La seconda e la terza sottostringa dell'array src non vengono affatto analizzate.

C11

Le librerie C standard non contengono una versione thread-safe o re-entrant ma altre, come POSIX ' strtok_r . Nota che su MSVC l'equivalente strtok , strtok_s è thread-safe.

C11

C11 ha una parte opzionale, Annex K, che offre una versione thread-safe e re-entry chiamata strtok_s . Puoi provare la funzione con __STDC_LIB_EXT1__ . Questa parte facoltativa non è ampiamente supportata.

La funzione strtok_s differisce dalla funzione strtok_r POSIX strtok_r dall'archiviazione all'esterno della stringa che viene tokenizzata e controllando i vincoli di runtime. Sui programmi scritti correttamente, però, strtok_s e strtok_r comportano allo stesso modo.

Usando strtok_s con l'esempio ora si ottiene la risposta corretta, in questo modo:

/* you have to announce that you want to use Annex K */ 
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>

#ifndef __STDC_LIB_EXT1__
# error "we need strtok_s from Annex K"
#endif

char src[] = "1.2,3.5,4.2";  
char *next = NULL;
char *first = strtok_s(src, ",", &next);

do 
{
    char *part;
    char *posn;

    printf("[%s]\n", first);
    part = strtok_s(first, ".", &posn);
    while (part != NULL)
    {
        printf(" [%s]\n", part);
        part = strtok_s(NULL, ".", &posn);
    }
} 
while ((first = strtok_s(NULL, ",", &next)) != NULL);

E l'output sarà:

[1.2]
 [1]
 [2]
[3.5]
 [3]
 [5]
[4.2]
 [4]
 [2]