c strtok strsep

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



strdup (3)

¿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 una sobre la otra?


Del manual de la biblioteca GNU C - Encontrar tokens 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 si strsep devuelve una cadena vacía antes de procesarla.


La primera diferencia en strtok() y strep() es la forma en que manejan los caracteres delimitadores contiguos en la cadena de entrada.

Manejo de caracteres delimitadores contiguos 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 tras otro. strtok() no indica la aparición de caracteres delimitadores contiguos . Además, el strtok() modifica la cadena de entrada .

Manejo de caracteres delimitadores contiguos por strep() :

#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 strep() 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 a través de <empty> ) entre bbb y ccc . Esas dos cadenas vacías son para "--" entre "bbb" y "ccc" . Cuando strep() encontró un carácter delimitador '' '' después de "bbb" , reemplazó el carácter delimitador con el carácter ''/0'' y devolvió "bbb" . Después de esto, strep() encontró otro carácter delimitador ''-'' . Luego reemplazó el carácter delimitador con el carácter ''/0'' y devolvió la cadena vacía. Lo mismo es para el siguiente carácter delimitador.

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

El strsep() modifica la cadena de entrada, así como el puntero cuya dirección pasó como primer argumento a strsep() .

La segunda diferencia es que strtok() 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() .

Llamando a 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 imprime el token "aaa" y no imprime el resto de los tokens de la cadena de entrada porque llama a another_function_callng_strtok() que a su vez llama a strtok() y establece el puntero estático de strtok() en NULL cuando Acaba con la extracción de todos los tokens. El control vuelve a function_callng_strtok() while loop, strtok() devuelve NULL debido al puntero estático que apunta a NULL y que hace que la condición del bucle sea false y que el bucle salga.

Llamando a 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í puede ver, llamar a strsep() antes de analizar completamente una cadena no hace ninguna diferencia.

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

De strsep :

La función strsep () está diseñada como un reemplazo para la función strtok (). Si bien la función strtok () debe preferirse por motivos de portabilidad (cumple con la norma 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 usado por más de una sola cadena a la vez. La función strsep () apareció por primera vez en 4.4BSD.

Para referencia:


Una diferencia importante 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 GNU C Biblioteca, y se originó en BSD). Por lo tanto, es más probable que el código portátil use strtok() que strsep() .

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

La página del manual para strsep() en kernel.org dice:

La función strsep () se introdujo como un reemplazo para strtok (3), ya que esta última no puede manejar campos vacíos.

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

Tanto strsep() como strtok() modifican 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?

  • strsep() cuando quiera tokens vacíos en lugar de permitir múltiples delimitadores entre tokens, y cuando no le importe la portabilidad.
  • strtok_r() usar strtok_r() cuando quiera permitir múltiples delimitadores entre tokens y no quiere tokens vacíos (y POSIX es lo suficientemente portátil para usted).
  • Solo strtok() cuando alguien amenace tu vida si no lo haces. Y solo lo usarías durante el tiempo suficiente para salir de la situación que amenaza la vida; Entonces abandonarías todo uso de él una vez más. Es venenoso No lo uses. Sería mejor escribir tus propios strtok_r() o strsep() que usar strtok() .

¿Por qué es strtok() venenoso?

La strtok() 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 usa strtok() y llama a su función que también usa strtok() , interrumpe la función de llamada.
  2. Si su función llama a cualquier función que llame a strtok() , interrumpirá el uso de su strtok() de strtok() .
  3. Si su programa es multiproceso, como máximo un hilo puede estar usando strtok() en un momento dado, a través de una secuencia de llamadas a strtok() .

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

  • Puedes usar strsep() si está disponible.
  • Puede usar strtok_r() POSIX si está disponible.
  • Puede usar strtok_s() Microsoft si está disponible.
  • Nominalmente, podría usar la función strtok_s() del ISO KEC.799.1 del Anexo K.3.7.3.1, pero su interfaz es diferente tanto de strtok_r() como de strtok_r() Microsoft.

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() .