C をブラッシュアップするために、便利なライブラリ コードをいくつか書いています。テキスト ファイルの読み取りに関しては、面倒な作業のほとんどを行う便利なトークン化関数があると常に便利です (ループstrtok
は不便で危険です)。
この関数を書いたとき、その複雑さに驚かされました。実を言うと、バグ (特に割り当てエラーの場合のメモリ リーク) が含まれているとほぼ確信しています。コードは次のとおりです。
/* Given an input string and separators, returns an array of
** tokens. Each token is a dynamically allocated, NUL-terminated
** string. The last element of the array is a sentinel NULL
** pointer. The returned array (and all the strings in it) must
** be deallocated by the caller.
**
** In case of errors, NULL is returned.
**
** This function is much slower than a naive in-line tokenization,
** since it copies the input string and does many allocations.
** However, it's much more convenient to use.
*/
char** tokenize(const char* input, const char* sep)
{
/* strtok ruins its input string, so we'll work on a copy
*/
char* dup;
/* This is the array filled with tokens and returned
*/
char** toks = 0;
/* Current token
*/
char* cur_tok;
/* Size of the 'toks' array. Starts low and is doubled when
** exhausted.
*/
size_t size = 2;
/* 'ntok' points to the next free element of the 'toks' array
*/
size_t ntok = 0;
size_t i;
if (!(dup = strdup(input)))
return NULL;
if (!(toks = malloc(size * sizeof(*toks))))
goto cleanup_exit;
cur_tok = strtok(dup, sep);
/* While we have more tokens to process...
*/
while (cur_tok)
{
/* We should still have 2 empty elements in the array,
** one for this token and one for the sentinel.
*/
if (ntok > size - 2)
{
char** newtoks;
size *= 2;
newtoks = realloc(toks, size * sizeof(*toks));
if (!newtoks)
goto cleanup_exit;
toks = newtoks;
}
/* Now the array is definitely large enough, so we just
** copy the new token into it.
*/
toks[ntok] = strdup(cur_tok);
if (!toks[ntok])
goto cleanup_exit;
ntok++;
cur_tok = strtok(0, sep);
}
free(dup);
toks[ntok] = 0;
return toks;
cleanup_exit:
free(dup);
for (i = 0; i < ntok; ++i)
free(toks[i]);
free(toks);
return NULL;
}
簡単な使い方は次のとおりです。
int main()
{
char line[] = "The quick brown fox jumps over the lazy dog";
char** toks = tokenize(line, " \t");
int i;
for (i = 0; toks[i]; ++i)
printf("%s\n", toks[i]);
/* Deallocate
*/
for (i = 0; toks[i]; ++i)
free(toks[i]);
free(toks);
return 0;
}
ああ、そしてstrdup
:
/* strdup isn't ANSI C, so here's one...
*/
char* strdup(const char* str)
{
size_t len = strlen(str) + 1;
char* dup = malloc(len);
if (dup)
memcpy(dup, str, len);
return dup;
}
tokenize
関数のコードについて注意すべき点がいくつかあります。
strtok
には、入力文字列を上書きするという失礼な癖があります。ユーザーのデータを保存するには、入力の複製に対してのみ呼び出します。を使用して複製を取得しstrdup
ます。strdup
ただし、ANSI-C ではないため、ANSI-C を作成する必要がありましたトークンの数が事前にわからないため、
toks
配列は で動的に拡張されます。realloc
テスト用の初期サイズは 2 です。実際のコードでは、おそらくもっと大きな値に設定します。また、ユーザーに返され、ユーザーは使用後に割り当てを解除する必要があります。いずれの場合も、リソースをリークしないように細心の注意を払っています。たとえば、
realloc
NULL を返す場合、古いポインターは実行されません。古いポインターが解放され、関数が戻ります。返されたときにリソースがリークすることはありませんtokenize
(ユーザーに返された配列を使用後に割り当てを解除する必要がある名目上のケースを除きます)。- Aは、場合によっては適切な哲学
goto
に従って、より便利なクリーンアップ コードに使用されます(これは良い例です。IMHO)。goto
次の関数は、1 回の呼び出しで単純な割り当て解除を行うのに役立ちます。
/* Given a pointer to the tokens array returned by 'tokenize',
** frees the array and sets it to point to NULL.
*/
void tokenize_free(char*** toks)
{
if (toks && *toks)
{
int i;
for (i = 0; (*toks)[i]; ++i)
free((*toks)[i]);
free(*toks);
*toks = 0;
}
}
SO の他のユーザーとこのコードについて話し合いたいと思います。何がもっとうまくできたでしょうか?そのようなトークナイザーに異なるインターフェイスをお勧めしますか? ユーザーからの割り当て解除の負担はどのように取り除かれますか? とにかくコードにメモリリークがありますか?
前もって感謝します