web-dev-qa-db-ja.com

Cのstrtokとstrsepの違いは何ですか

誰かがstrtok()strsep()の違いを教えてくれませんか?それらの長所と短所は何ですか?そして、なぜ私は他のものを選ぶのでしょうか?.

27
mizuki

GNU C Library manual- Finding Tokens in a String) から:

strsepstrtok_rの違いの1つは、入力文字列にデリミタの複数の文字が含まれている場合、strsepはデリミタの各文字ペアに対して空の文字列を返すことです。つまり、プログラムは通常、空の文字列を返すstrsepを処理する前にテストする必要があります。

7
George Gaál

strtok()strsep() の主な違いの1つは、strtok()が標準化されていることです。 POSIXによって)しかし strsep() は標準化されていません(CまたはPOSIXによって。これはGNU Cライブラリで利用可能で、BSDを起源としています)。したがって、移植可能なコードはstrtok()よりもstrsep()を使用する可能性が高くなります。

別の違いは、異なる文字列でのstrsep()関数の呼び出しはインターリーブできますが、strtok()ではできません(strtok_r()では可能です)。したがって、ライブラリでstrsep()を使用しても他のコードが誤って破損することはありませんが、strtok()を同時に使用する他のコードでは同時に使用できないため、ライブラリ関数でstrtok()を使用することを文書化する必要があります。ライブラリ関数を呼び出します。

kernel.org にあるstrsep()のマニュアルページには、

Strsep()関数は空のフィールドを処理できないため、strtok(3)の代わりとして導入されました。

したがって、他の主な違いは、彼の回答で GeorgeGaál によって強調されている違いです。 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()呼び出しのシーケンス全体で、最大1つのスレッドがいつでもstrtok()を使用できます。

この問題の原因は、strtok()が中断したところから続行できるようにする、呼び出し間で保存された状態です。 「strtok()を使用しない」以外に問題を修正する賢明な方法はありません。

  • strsep()が使用可能な場合は使用できます。
  • 利用可能な場合は、POSIXの strtok_r() を使用できます。
  • Microsoftの strtok_s() が使用可能な場合は、それを使用できます。
  • 通常、ISO/IEC 9899:2011 Annex K.3.7.3.1関数strtok_s()を使用できますが、そのインターフェイスは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つの引数があり、strtok()の他の2つのバリアントのように3つではないことに注意してください。

41

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
_

出力では、bbbcccの間に2つの空の文字列(_<empty>_で示される)が表示されます。これら2つの空の文字列は、_"--"_と_"bbb"_の間の_"ccc"_用です。 _' '_の後にstrep()が区切り文字_"bbb"_を検出すると、区切り文字を_'\0'_文字に置き換え、_"bbb"_を返しました。この後、strep()は別の区切り文字_'-'_を見つけました。次に、区切り文字を_'\0'_文字に置き換え、空の文字列を返しました。次の区切り文字についても同様です。

strsep()がnull文字へのポインタを返すときに、連続する区切り文字が示されます(つまり、値_'\0'_の文字)。

strsep()入力文字列とポインタを変更最初の引数としてstrsep()に渡されたアドレス。

2番目の違いは、strtok()は静的変数に依存して、文字列内の現在の解析位置を追跡することです。この実装では2番目の文字列を開始する前に1つの文字列を完全に解析するが必要です。しかし、これは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ループに戻り、NULLを指す静的ポインターが原因でstrtok()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.
_

ここで、1つの文字列を完全に解析する前にstrsep()を呼び出しても違いはありません。

したがって、strtok()strsep()の欠点は、どちらも入力文字列を変更することですが、strsep()には、上記のようにstrtok()よりもいくつかの利点があります。 。

strsep から:

Strsep()関数は、strtok()関数の代替として意図されています。移植性の理由から、strtok()関数の方をお勧めしますが(ISO/IEC 9899:1990(「ISO C90」)に準拠)、空のフィールドを処理できません。つまり、2つの隣接する区切り文字で区切られたフィールドを検出できます。または、一度に複数の文字列に使用されます。 strsep()関数は4.4BSDではじめて登場しました。


参考のため:

2
H.S.