S-JIS[2006-10-17/2006-10-30]

strtok関数

strtok()は、C言語で 1つの文字列を指定した区切り文字(例えばカンマとか)で分割する関数。
(strtokの関数名は、たぶんstring token)
元となる文字列が変更されるので、非推奨な関数ではあるが…他にいいのが無いからしょうがない。
Javaのsplitみたいに配列にして返してくれれば便利なんだけど、C言語ではメモリの扱いをきちんとしなければならないので、なかなか面倒。

そこで、strtok()を使って単語に分割し、配列にして返す例を作ってみた。

#include <string.h>
/*
指定された区切り文字で分割した文字列の配列を返す。配列の最後尾はNULLになっている。
エラーの場合はNULLを返す。
使用後はsplit_free()を呼び出してメモリを解放する必要がある。
strtok()を使っているので、MT非セーフ。
*/
char** split(const char *str, const char *delim)
{
	char **arr = NULL; //配列
	int    n   = 0;    //配列の個数
	char *buf, *tp;

	if (str == NULL || delim == NULL) {
		return NULL;
	}

	buf = (char*)malloc(strlen(str) + 1);
	if (buf == NULL) {
		return NULL;
	}
	strcpy(buf, str);

	for(tp = strtok(buf, delim); tp; tp = strtok(NULL, delim)) {
		char **a = (char**)realloc(arr, sizeof(*arr) * (n+3));
		if (a == NULL) {
			free(arr);
			free(buf);
			return NULL;
		}
		a[++n] = tp;
		arr = a;
	}

	if (arr != NULL) {
		arr[0] = buf;	//元のバッファを保持する
		arr++;
		arr[n] = NULL;	//終端
	}

	return arr;
}

/*
split()で確保したメモリを解放する。
*/
void split_free(char **arr)
{
	if (arr != NULL) {
		arr--;
		free(arr[0]);	//元のバッファを解放する
		free(arr);
	}
}
/*使用例*/
	char **arr = split("aaa,bb,cccc", ",");
//	char **arr = split("aaaccc", ",");
//	char **arr = split(",a1,a2,a3", ",");
//	char **arr = split(",a1,,a3,,", ",");
	if (arr != NULL) {
		int i;
		for(i = 0; arr[i] != NULL; i++) {
			printf("%d:%s\n", i, arr[i]);
		}
		split_free(arr);
	}

strtok()は元の文字列の区切り文字を空文字('\0')に置き換えて、それぞれの先頭アドレスを返す。
したがって、本当の大元の文字列を書き換えないようにするためにbufを確保してコピーし、それをstrtok()の引数にしている。

また、配列はrealloc()を使って確保しているが、直接arr=realloc(arr,〜)としちゃいけないのがまた面倒。
どうでもいいプログラムなら、メモリ確保できなかった場合の戻り値NULLなんかは無視しちゃうんだけどねー(苦笑)

戻り値は一つしか返せない(引数でポインター渡しにするのは嫌だ)ので、配列の個数を返したいところだけど、諦める。
NULLで終端とすることにする。(main()関数の第三引数のenvpと一緒)

さらに、元となるbufもどこかで保持しなければならない。strtok()が返すのはbuf内の位置だから。
(大抵の実装の場合、)普通はarr[0]がbufの先頭アドレスと等しくなるんだけど、区切り文字で始まっている文字列の場合、配列の要素に空文字列は入らないので、arr[0]とbufは一致しない。
(strtok()では、空文字列は無視される)

文字列のアドレスと値のイメージ
通常の場合の例   カンマから始まる例
str buf arr str buf arr
C000 a D000 a arr[0] D000 C000 , D000      
C001 , D001 \0     C001 a D001 a arr[0] D001
C002 b D002 b arr[1] D002 C002 , D002 \0    
C003 \0 D003 \0     C003 b D003 b arr[1] D003
            C004 \0 D004 \0    

という訳で、配列arrにさらにポインター一個分の要素を確保し、bufを保持しておくことにした。
保持する場所はNULL終端の後ろでも良かったんだけど、それだと解放時にNULLをサーチしないといけなくなるので、配列の先頭にした。なので、split()が返すポインターは 確保した領域の先頭からは1つずれている。

…要するに、strtok()を使うのであれば、strtok()の戻り値を直接その場でそのまま使ってしまう方が効率はいい、ということかなー…。


C言語目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま