Каковы различия между strtok и strsep в C - PullRequest
23 голосов
/ 28 августа 2011

Может ли кто-нибудь объяснить мне, какие различия существуют между strtok() и strsep()?Каковы их преимущества и недостатки?И зачем мне выбирать один над другим?

Ответы [ 3 ]

41 голосов
/ 28 августа 2011

Одно существенное различие между strtok() и strsep() состоит в том, что strtok() стандартизирован (стандартом C, а следовательно, и POSIX), но strsep() не стандартизирован (C или POSIX; он доступен в библиотеке GNU C и создан на BSD). Таким образом, переносимый код с большей вероятностью будет использовать strtok(), чем strsep().

Другое отличие состоит в том, что вызовы функции strsep() в разных строках могут чередоваться, тогда как вы не можете сделать это с помощью strtok() (хотя вы можете сделать с помощью strtok_r()). Таким образом, использование strsep() в библиотеке не может случайно нарушить другой код, тогда как использование strtok() в библиотечной функции должно быть задокументировано, поскольку другой код, использующий strtok() в то же время, не может вызвать библиотечную функцию.

Страница руководства для strsep() на kernel.org говорит:

Функция strsep () была введена вместо strtok (3), поскольку последняя не может обрабатывать пустые поля.

Таким образом, другое важное отличие - это то, что Джордж Гаал выделил в своем ответе; strtok() разрешает несколько разделителей между одним токеном, тогда как strsep() ожидает один разделитель между токенами и интерпретирует соседние разделители как пустой токен.

Оба strsep() и strtok() изменяют свои входные строки, и ни один из них не позволяет определить, какой символ-разделитель помечает конец токена (поскольку оба пишут NUL '\0' через разделитель после конца токена).

Когда их использовать?

  • Вы будете использовать strsep(), когда вам нужны пустые токены, а не разрешать множественные разделители между токенами, и когда вы не против переносимости.
  • Вы должны использовать strtok_r(), если хотите разрешить несколько разделителей между токенами и не хотите пустых токенов (а POSIX достаточно переносим для вас).
  • Вы бы использовали strtok(), только если кто-то угрожает вашей жизни, если вы этого не сделаете. И вы будете использовать его только достаточно долго, чтобы вытащить вас из опасной для жизни ситуации; Вы бы тогда отказались от любого использования этого еще раз. Это ядовито; не используйте его. Было бы лучше написать свой собственный strtok_r() или strsep(), чем использовать strtok().

Почему strtok() ядовит?

Функция strtok() ядовита, если используется в библиотечной функции. Если ваша библиотечная функция использует strtok(), она должна быть четко задокументирована.

Это потому что:

  1. Если какая-либо вызывающая функция использует strtok() и вызывает вашу функцию, которая также использует strtok(), вы прерываете вызывающую функцию.
  2. Если ваша функция вызывает любую функцию, которая вызывает strtok(), это нарушит использование вашей функции strtok().
  3. Если ваша программа многопоточная, самое большее один поток может использовать strtok() в любой момент времени - для последовательности strtok() вызовов.

Корень этой проблемы - сохраненное состояние между вызовами, которое позволяет strtok() продолжать работу с того места, где оно было прервано. Не существует разумного способа решения проблемы, кроме «не использовать strtok()».

  • Вы можете использовать strsep(), если оно доступно.
  • Вы можете использовать POSIX strtok_r(), если он доступен.
  • Вы можете использовать Microsoft strtok_s(), если он доступен.
  • Номинально, вы можете использовать функцию ISO / IEC 9899: 2011 Приложение K.3.7.3.1 strtok_s(), но ее интерфейс отличается от strtok_r() и от 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);

Приложение K strtok_s():

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

Обратите внимание, что здесь есть 4 аргумента, а не 3, как в двух других вариантах strtok().

7 голосов
/ 28 августа 2011

Из руководства по библиотеке GNU C - Поиск токенов в строке :

Одно из различий между strsep и strtok_r состоит в том, что если входная строка содержит более одного символа из разделителя в строке strsep возвращает пустую строку для каждой пары символов из разделителя. Это означает, что программа обычно должна проверять, чтобы strsep возвращала пустую строку перед обработкой.

2 голосов
/ 03 декабря 2018

Первое различие в strtok() и strep() заключается в способе обработки непрерывных символов разделителя во входной строке.

Обработка непрерывных символов разделителя с помощью 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;
}

Вывод:

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

В выводе вы можете видеть токены "bbb" и "ccc" один за другим.strtok() не указывает на наличие смежных символов-разделителей .Кроме того, strtok() изменяет строку ввода .

Обработка непрерывных символов-разделителей с помощью 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;
}

Вывод:

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

В выводе вы можете видеть две пустые строки (обозначенные <empty>) между bbb и ccc.Эти две пустые строки предназначены для "--" между "bbb" и "ccc".Когда strep() обнаружил символ разделителя ' ' после "bbb", он заменил символ разделителя символом '\0' и вернул "bbb".После этого strep() обнаружил еще один символ-разделитель '-'.Затем он заменил символ разделителя на '\0' и вернул пустую строку.То же самое относится и к следующему символу разделителя.

Смежные символы-разделители указываются, когда strsep() возвращает указатель на нулевой символ (то есть символ со значением '\0').

strsep() изменяет входную строку, а также указатель , адрес которого передан в качестве первого аргумента strsep().

Второе отличие состоит в том, что strtok() полагаетсяна статическую переменную, чтобы отслеживать текущее местоположение разбора в строке.Эта реализация требует, чтобы полностью проанализировал одну строку перед началом второй строки .Но дело обстоит не так с strsep().

Вызовом strtok(), когда другой strtok() не закончен:

#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;
}

Выход:

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

Функция function_callng_strtok() печатает только токен "aaa" и не печатает остальные токены входной строки, потому что она вызывает another_function_callng_strtok(), что, в свою очередь, вызывает strtok() и устанавливает статический указатель strtok() на NULLкогда он заканчивается извлечением всех жетонов.Элемент управления возвращается к циклу function_callng_strtok() while, strtok() возвращает NULL из-за статического указателя, указывающего на NULL и создающего условие цикла false и выход из цикла.

Вызовstrsep() когда еще один strsep() не закончен:

#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;
}

Вывод:

# ./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.

Здесь вы можете увидеть, что вызов strsep() до полного разбора одной строки не делаетлюбая разница.

Итак, недостатком strtok() и strsep() является то, что оба изменяют входную строку, но strsep() имеет несколько преимуществ по сравнению с strtok(), как показано выше.

От strsep :

Функция strsep () предназначена для замены функции strtok ().Хотя функция strtok () должна быть предпочтительной по причинам переносимости (она соответствует ISO / IEC 9899: 1990 (`` ISO C90 '')), она не может обрабатывать пустые поля, то есть обнаруживать поля, разделенные двумя соседними символами-разделителями,или использоваться для более чем одной строки одновременно.Функция strsep () впервые появилась в 4.4BSD.


Для справки:

...