web-dev-qa-db-ja.com

strtok()の問題:トークンが区切り文字で区切られている場合、最後のトークンが区切り文字とnull '\ 0'の間にあるのはなぜですか?

次のプログラムでは、strtok()は大部分で期待どおりに機能しますが、1つの発見の背後にある理由を理解することはできません。 strtok()について読んだことがあります。

トークンの開始と終了を判別するために、関数は最初に開始位置から区切り文字に含まれていない最初の文字(トークンの開始になる)をスキャンします。次に、トークンのこの先頭からスキャンを開始します区切り文字に含まれる最初の文字をスキャンします。これがトークンの末尾になります。

出典: http://www.cplusplus.com/reference/cstring/strtok/

そして、ご存知のように、strtok()は各トークンの最後に_\0_を配置します。ただし、次のプログラムでは、最後の区切り文字はドット(_._)であり、その後にそのドットと引用符(_"_)の間にToadがあります。これで、ドットは私のプログラムの区切り文字になりましたが、Toadの後に区切り文字はなく、空白(私のプログラムの区切り文字)もありません。この前提から生じる次の混乱を解消してください。

strtok()が2つの区切り文字の間にないのに、Toadをトークンと見なすのはなぜですか?これは、NULL文字(_\0_)が検出されたときにstrtok()について読んだものです。

Strtokの呼び出しでstrの終了ヌル文字が見つかると、最初の引数としてnullポインターを使用してこの関数を呼び出すと、それ以降はすべてnullポインターが返されます。

出典: http://www.cplusplus.com/reference/cstring/strtok/

ヌル文字が検出されると、トークンの先頭へのポインタが返されるとはどこにも書かれていません(区切り文字が見つからなかったため、トークンの末尾を取得できなかったため、ここにはトークンさえありません)トークンの先頭から(つまり、Toadの「T」から)スキャンを開始した後、ヌル文字のみが見つかりました区切り文字ではありません)。では、なぜ引数文字列の最後の区切り文字と引用符の間の部分strtok()によってトークンと見なされるのですか?これを説明してください。

コード:

_#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] =" Falcon,eagle-hawk..;buzzard,gull..pigeon sparrow,hen;owl.Toad";
  char * pch=strtok(str," ;,.-");

    while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, " ;,.-");
  }

  return 0;
}
_

出力:

ファルコン


ノスリ
カモメ

スズメ

フクロウ
ヒキガエル

7

規格のstrtok(7.24.5.8)の仕様はかなり明確です。特に、私がそれを正しく理解している場合、パラグラフ4(私が追加した強調)は質問に直接関連しています。

3シーケンスの最初の呼び出しは、s1が指す文字列を検索して、s2が指す現在の区切り文字列に含まれていない最初の文字を探します。そのような文字が見つからない場合、s1が指す文字列にはトークンがなく、strtok関数はnullポインタを返します。そのような文字が見つかった場合、それは最初のトークンの始まりです。

4次に、strtok関数は、そこから現在の区切り文字列に含まれている文字を検索します。 そのような文字が見つからない場合、現在のトークンはs1が指す文字列の末尾まで拡張され、その後のトークンの検索ではnullポインタが返されます。そのような文字が見つかった場合、それはnull文字で上書きされ、現在のトークンが終了します。 strtok関数は、次の文字へのポインターを保存し、そこからトークンの次の検索が開始されます。

通話中

char *where = strtok(string_or_NULL, delimiters);

返されるトークン(が存在する場合)は、開始位置(両端を含む)から見つかった最初の非区切り文字から、次の区切り文字(存在する場合)まで、または文字列の終わりまで拡張されます。それ以降の区切り文字が存在しない場合。

リンクされた説明では、標準とは対照的に、文字列の最後まで拡張されるトークンのケースについて明示的に言及されていないため、その点では不完全です。

9
Daniel Fischer

strtok() のPOSIXの説明に行くと、説明には次のように書かれています。

char *strtok(char *restrict s1, const char *restrict s2);

strtok()への一連の呼び出しは、s1が指す文字列をトークンのシーケンスに分割します。各トークンは、s2が指す文字列から1バイトで区切られます。シーケンスの最初の呼び出しには、最初の引数としてs1があり、その後に最初の引数としてnullポインターを使用した呼び出しが続きます。 s2が指す区切り文字列は、呼び出しごとに異なる場合があります。

シーケンスの最初の呼び出しは、s1が指す文字列を検索して、s2が指す現在の区切り文字列に含まれていない最初のバイトを探します。そのようなバイトが見つからない場合、s1が指す文字列にはトークンがなく、strtok()はnullポインタを返します。そのようなバイトが見つかった場合、それは最初のトークンの始まりです。

次に、strtok()関数は、そこから現在の区切り文字列に含まれているバイトを検索します。そのようなバイトが見つからない場合、現在のトークンはs1が指す文字列の末尾まで拡張され、その後のトークンの検索ではnullポインタが返されます。そのようなバイトが見つかると、NUL文字で上書きされ、現在のトークンが終了します。 strtok()関数は、次のバイトへのポインタを保存します。このバイトから、トークンの次の検索が開始されます。

3番目の段落の2番目の文に注意してください。

そのようなバイトが見つからない場合、現在のトークンはs1が指す文字列の末尾まで拡張され、その後のトークンの検索ではnullポインタが返されます。

これは、質問の例では、Toadが実際にトークンであることを明確に示しています。これを考える1つの方法は、区切り文字のリストには、区切り文字列の最後に常にNUL '\0'が含まれているということです。


それを診断した後、strtok()は使用するのに適した関数ではないことに注意してください。スレッドセーフまたは再入可能ではありません。 Windowsでは、代わりに strtok_s() を使用できます。 Unixでは、通常、 strtok_r() を使用できます。これらは、検索を再開するポインタを内部に格納しないため、より優れた関数です。

strtok()は再入可能ではないため、strtok()を使用しているときに、strtok()を使用する関数内からstrtok()を使用する関数を呼び出すことはできません。また、strtok()を使用しているライブラリ関数は、strtok()を使用している関数から呼び出すことができないため、使用していることを明確に識別する必要があります。したがって、strtok()を使用すると、生活が困難になります。

strtok()ファミリーの関数(および関連する strsep() )のもう1つの問題は、区切り文字が上書きされることです。トークナイザーが文字列をトークン化した後、区切り文字が何であったかを知ることはできません。これは、一部のアプリケーション(シェルコマンドラインの解析など。区切り文字がパイプ、セミコロン、アンパサンド(または...)のいずれであるかが重要です)で問題になる可能性があります。したがって、シェルパーサーは通常strtok()を使用しません。パーサーがstrtok()を使用するシェルについて、SO)に関する質問が多数あるにもかかわらず。

一般に、プレーンなstrtok()を避けて、strtok_r()またはstrtok_s()のどちらが目的に適しているかを判断するのはあなた次第です。

4

Cplusplus.comがすべてを語っているわけではないからです。 Cppreference.com より適切な説明があります。

Cplusplus.comは、strtokがスレッドセーフではないことにも言及しておらず、C++プログラミング言語のstrtok関数のみを文書化していますが、cppreference.comはスレッドセーフの問題に言及し、 [〜#〜] c [〜#〜]C++ プログラミング言語の両方のstrtok関数。

2
Oktalist

strtokは、指定された区切り文字で区切られた一連のトークンに文字列を分割します。区切り文字はトークンを区切るだけで、必ずしも両側で終了するわけではありません。

0
gkovacs90