Подтвердить что ты не робот

Каковы различия между strtok и strsep в C

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

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

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

Ответ 3

Первое отличие в 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 loop, 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.


Для справки: