¿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
ystrtok_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 sistrsep
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()
usarstrtok_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 propiosstrtok_r()
ostrsep()
que usarstrtok()
.
¿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:
- Si alguna función de llamada usa
strtok()
y llama a su función que también usastrtok()
, interrumpe la función de llamada. - Si su función llama a cualquier función que llame a
strtok()
, interrumpirá el uso de sustrtok()
destrtok()
. - 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 astrtok()
.
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 destrtok_r()
como destrtok_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()
.