¿Cuáles son las diferencias entre strtok y strsep en C?

¿Podría alguien explicarme qué diferencias hay entre strtok() y strsep()? ¿Cuáles son las ventajas y desventajas de ellos? ¿Y por qué elegiría uno sobre el otro?

preguntado el 28 de agosto de 11 a las 02:08

3 Respuestas

Una gran diferencia entre strtok() y strsep() es que strtok() está estandarizado (por el estándar C, y por lo tanto también por POSIX) pero strsep() no está estandarizado (por C o POSIX; está disponible en la biblioteca GNU C y se originó en BSD). Por lo tanto, es más probable que el código portátil utilice strtok() que strsep().

Otra diferencia es que las llamadas al strsep() La función en diferentes cadenas se puede intercalar, mientras que no se puede hacer eso con strtok() (aunque puedes con strtok_r()). Entonces, usando strsep() en una biblioteca no rompe otro código accidentalmente, mientras que el uso strtok() en una función de biblioteca debe documentarse porque otro código que usa strtok() al mismo tiempo, no puede llamar a la función de biblioteca.

La página de manual para strsep() at kernel.org dice:

La función strsep () se introdujo como reemplazo de strtok (3), ya que este último no puede manejar campos vacíos.

Por lo tanto, la otra gran diferencia es la resaltada por George Gaál en su respuesta; strtok() permite múltiples delimitadores entre un solo token, mientras que strsep() espera un solo delimitador entre tokens e interpreta los delimitadores adyacentes como un token vacío.

Ambos strsep() y strtok() modificar sus cadenas de entrada y ninguno le permite identificar qué carácter delimitador marcó el final del token (porque ambos escriben un NUL '\0' sobre el separador después del final del token).

¿Cuándo usarlos?

  • Usarías strsep() cuando desea tokens vacíos en lugar de permitir múltiples delimitadores entre tokens, y cuando no le importa la portabilidad.
  • Usarías strtok_r() cuando desea permitir múltiples delimitadores entre tokens y no desea tokens vacíos (y POSIX es lo suficientemente portátil para usted).
  • Solo usarías strtok() cuando alguien amenaza tu vida si no lo haces. Y solo lo usaría el tiempo suficiente para salir de la situación que amenaza su vida; entonces dejaría de usarlo una vez más. Es venenoso; No lo uses. Sería mejor que escribieras el tuyo strtok_r() or strsep() que utilizar strtok().

¿Por qué es strtok() ¿venenoso?

La strtok() La función es venenosa si se usa en una función de biblioteca. Si su función de biblioteca usa strtok(), debe documentarse claramente.

Eso es porque:

  1. Si alguna función de llamada está usando strtok() y llama a tu función que también usa strtok(), rompes la función de llamada.
  2. Si su función llama a cualquier función que llame strtok(), eso romperá el uso de su función de strtok().
  3. Si su programa es multiproceso, como máximo se puede usar un hilo strtok() en un momento dado, a través de una secuencia de strtok() llamadas.

La raíz de este problema es el estado guardado entre llamadas que permite strtok() para continuar donde lo dejó. No existe una forma sensata de solucionar el problema que no sea "no usar strtok()".

  • Puedes usar strsep() si está disponible
  • Puedes usar POSIX's strtok_r() si está disponible
  • Puede utilizar Microsoft strtok_s() si está disponible
  • Nominalmente, podría utilizar la función ISO / IEC 9899: 2011 Anexo K.3.7.3.1 strtok_s(), pero su interfaz es diferente de ambas strtok_r() y de Microsoft strtok_s().

BSD strsep():

char *strsep(char **stringp, const char *delim);

POSIX strtok_r():

char *strtok_r(char *restrict s, const char *restrict sep, char **restrict state);

Microsoft strtok_s():

char *strtok_s(char *strToken, const char *strDelimit, char **context);

Anexo K strtok_s():

char *strtok_s(char * restrict s1, rsize_t * restrict s1max,
               const char * restrict s2, char ** restrict ptr);

Tenga en cuenta que esto tiene 4 argumentos, no 3 como en las otras dos variantes en strtok().

Respondido 01 Oct 17, 00:10

Tenga en cuenta que el anexo K strtok_s() se declara como: char *strtok_s(char * restrict s1, rsize_t * restrict s1max, const char * restrict s2, char ** restrict ptr); que no coincide con la interfaz de Microsoft strtok_s() o POSIX's strtok_r(). Incluso si se implementó, la diferencia es molesta: limita la utilidad de la función del Anexo K. Ver también ¿Utiliza las funciones 'seguras' de TR 24731? - Jonathan Leffler

Obtuve muy poca información real de su explicación altamente calificada. Digo esto solo porque parece que cree que su explicación puede ser bastante exhaustiva y puede aclarar todo el asunto y probablemente lo haga para las personas de alta inteligencia, pero para mí fue casi completamente opaco. Realmente no me responde la pregunta en absoluto. Tuve que buscar el término intercalado (capas alternas). Así que supongo que no hay subprocesos múltiples. "múltiples delimitadores entre un solo token" ¿Por 'token' te refieres a una subcadena? Pero cada llamada crea una subcadena cuando escribe un '\ 0'. Estoy totalmente confundido. - iamoumuamua

Lamento que no hayas obtenido información de mi respuesta, @iamoumuamua. La especificación de strtok() dice: Una secuencia de llamadas al strtok función rompe la cadena apuntada por s1 en una secuencia de tokens, cada uno de los cuales está delimitado por un carácter de la cadena apuntada por s2. Entonces, los tokens son lo que strtok() identifica. [… Continúa 1…] - Jonathan Leffler

[… Continuación 1…] La declaración "llama a la strsep() La función en diferentes cadenas se puede intercalar, mientras que no se puede hacer eso con strtok()"significa que puedes usar strsep() para cortar y cortar en dados dos cadenas diferentes en paralelo, tomando primero una ficha de string1 luego una ficha de string2, mientras que strtok() requiere que te dividas completamente string1 antes de abordar string2 o viceversa. Sí, eso significa que no hay subprocesos múltiples con strtok(), pero también restringe severamente los programas de un solo subproceso. [… Continúa 2…] - Jonathan Leffler

[… Continuación 2…] Las fichas encontradas por strtok() y strsep() están separados por delimitadores. Con strtok(), varios delimitadores adyacentes se tratan como parte de un solo espacio entre tokens (por lo que no puede tener tokens vacíos con strtok()), mientras que strsep() asume que cada token está separado del siguiente por un solo delimitador, y dos delimitadores adyacentes significan que hay un token vacío entre ellos. - Jonathan Leffler

Del manual de la biblioteca GNU C - Encontrar fichas en una cadena:

Una diferencia entre strsep y strtok_r es que si la cadena de entrada contiene más de un carácter del delimitador en una fila strsep devuelve una cadena vacía para cada par de caracteres del delimitador. Esto significa que un programa normalmente debería probar strsep devolver una cadena vacía antes de procesarla.

Respondido el 07 de diciembre de 14 a las 02:12

¿Puedes darme un ejemplo? Por favor, estoy un poco confundido. mizuki

Puede encontrar ejemplos del uso de estas funciones si hace clic en enlace :-) También tenga en cuenta que strsep La función puede estar ausente en su compilador de C. - George Gaál

Primera diferencia en strtok() y strsep() es la forma en que manejan los caracteres delimitadores contiguos en la cadena de entrada.

Caracteres delimitadores contiguos manejados por strtok():

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

int main(void) {
    const char* teststr = "aaa-bbb --ccc-ddd"; //Contiguous delimiters between bbb and ccc sub-string
    const char* delims = " -";  // delimiters - space and hyphen character
    char* token;
    char* ptr = strdup(teststr);

    if (ptr == NULL) {
        fprintf(stderr, "strdup failed");
        exit(EXIT_FAILURE);
    }

    printf ("Original String: %s\n", ptr);

    token = strtok (ptr, delims);
    while (token != NULL) {
        printf("%s\n", token);
        token = strtok (NULL, delims);
    }

    printf ("Original String: %s\n", ptr);
    free (ptr);
    return 0;
}

Salida:

# ./example1_strtok
Original String: aaa-bbb --ccc-ddd
aaa
bbb
ccc
ddd
Original String: aaa

En la salida, puedes ver el token. "bbb" y "ccc" Uno después del otro. strtok() no indica la aparición de caracteres delimitadores contiguos. También el strtok() modificar la cadena de entrada.

Caracteres delimitadores contiguos manejados por strsep():

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

int main(void) {
    const char* teststr = "aaa-bbb --ccc-ddd"; //Contiguous delimiters between bbb and ccc sub-string
    const char* delims = " -";  // delimiters - space and hyphen character
    char* token;
    char* ptr1;
    char* ptr = strdup(teststr);

    if (ptr == NULL) {
        fprintf(stderr, "strdup failed");
        exit(EXIT_FAILURE);
    }

    ptr1 = ptr;

    printf ("Original String: %s\n", ptr);
    while ((token = strsep(&ptr1, delims)) != NULL) {
        if (*token == '\0') {
            token = "<empty>";
        }
        printf("%s\n", token);
    }

    if (ptr1 == NULL) // This is just to show that the strsep() modifies the pointer passed to it
        printf ("ptr1 is NULL\n");
    printf ("Original String: %s\n", ptr);
    free (ptr);
    return 0;
}

Salida:

# ./example1_strsep
Original String: aaa-bbb --ccc-ddd
aaa
bbb
<empty>             <==============
<empty>             <==============
ccc
ddd
ptr1 is NULL
Original String: aaa

En la salida, puede ver las dos cadenas vacías (indicadas mediante <empty>) Entre bbb y ccc. Esas dos cadenas vacías son para "--" entre "bbb" y "ccc". ¿Cuándo strsep() encontró un carácter delimitador ' ' Si lo envía después "bbb", reemplazó el carácter delimitador con '\0' personaje y regresó "bbb". Después de este, strsep() encontró otro carácter delimitador '-'. Luego reemplazó el carácter delimitador con '\0' carácter y devolvió la cadena vacía. Lo mismo ocurre con el siguiente carácter delimitador.

Los caracteres delimitadores contiguos se indican cuando strsep() devuelve un puntero a un carácter nulo (es decir, un personaje con el valor '\0').

La strsep() modificar la cadena de entrada y el puntero cuya dirección pasó como primer argumento a strsep().

La segunda diferencia es, strtok() se basa en una variable estática para realizar un seguimiento de la ubicación actual del análisis dentro de una cadena. Esta implementación requiere analizar completamente una cadena antes de comenzar una segunda cadena. Pero este no es el caso con strsep().

llamar strtok() cuando otro strtok() no está terminado:

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

void another_function_callng_strtok(void)
{
    char str[] ="ttt -vvvv";
    char* delims = " -";
    char* token;

    printf ("Original String: %s\n", str);
    token = strtok (str, delims);
    while (token != NULL) {
        printf ("%s\n", token);
        token = strtok (NULL, delims);
    }
    printf ("another_function_callng_strtok: I am done.\n");
}

void function_callng_strtok ()
{
    char str[] ="aaa --bbb-ccc";
    char* delims = " -";
    char* token;

    printf ("Original String: %s\n", str);
    token = strtok (str, delims);
    while (token != NULL)
    {
        printf ("%s\n",token);
        another_function_callng_strtok();
        token = strtok (NULL, delims);
    }
}

int main(void) {
    function_callng_strtok();
    return 0;
}

Salida:

# ./example2_strtok
Original String: aaa --bbb-ccc
aaa
Original String: ttt -vvvv
ttt
vvvv
another_function_callng_strtok: I am done.

La función function_callng_strtok() solo imprimir token "aaa" y no imprime el resto de los tokens de la cadena de entrada porque llama another_function_callng_strtok() que a su vez llama strtok() y estableció el puntero estático de strtok() a NULL cuando termina de extraer todos los tokens. El control vuelve a function_callng_strtok() while lazo, strtok() devoluciones NULL debido al puntero estático que apunta a NULL y que hacen que la condición de bucle false y salidas de bucle.

llamar strsep() cuando otro strsep() no está terminado:

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

void another_function_callng_strsep(void)
{
    char str[] ="ttt -vvvv";
    const char* delims = " -";
    char* token;
    char* ptr = str;

    printf ("Original String: %s\n", str);
    while ((token = strsep(&ptr, delims)) != NULL) {
        if (*token == '\0') {
            token = "<empty>";
        }
        printf("%s\n", token);
    }
    printf ("another_function_callng_strsep: I am done.\n");
}

void function_callng_strsep ()
{
    char str[] ="aaa --bbb-ccc";
    const char* delims = " -";
    char* token;
    char* ptr = str;

    printf ("Original String: %s\n", str);
    while ((token = strsep(&ptr, delims)) != NULL) {
        if (*token == '\0') {
            token = "<empty>";
        }
        printf("%s\n", token);
        another_function_callng_strsep();
    }
}

int main(void) {
    function_callng_strsep();
    return 0;
}

Salida:

# ./example2_strsep
Original String: aaa --bbb-ccc
aaa
Original String: ttt -vvvv
ttt
<empty>
vvvv
another_function_callng_strsep: I am done.
<empty>
Original String: ttt -vvvv
ttt
<empty>
vvvv
another_function_callng_strsep: I am done.
<empty>
Original String: ttt -vvvv
ttt
<empty>
vvvv
another_function_callng_strsep: I am done.
bbb
Original String: ttt -vvvv
ttt
<empty>
vvvv
another_function_callng_strsep: I am done.
ccc
Original String: ttt -vvvv
ttt
<empty>
vvvv
another_function_callng_strsep: I am done.

Aquí puedes ver, llamando strsep() antes de analizar completamente una cadena no hace ninguna diferencia.

Entonces, la desventaja de strtok() y strsep() es que ambos modifican la cadena de entrada pero strsep() tiene un par de ventajas sobre strtok() como se ilustra arriba.

Desde strsep:

La función strsep () está pensada como un reemplazo de la función strtok (). Si bien la función strtok () debe preferirse por razones de portabilidad (cumple con ISO / IEC 9899: 1990 (`` ISO C90 '')), no puede manejar campos vacíos, es decir, detectar campos delimitados por dos caracteres delimitadores adyacentes, o para ser utilizado para más de una sola cadena a la vez. La función strsep () apareció por primera vez en 4.4BSD.


Para referencia:

Respondido 09 Abr '20, 07:04

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.