文字列からトークンの切り出し

呼称: 文字列からトークンの切り出し
目的: 文字列からデリミタでトークンに分解する
特徴: デリミタの文字の候補は複数指定することができる
用例: 設定ファイルのキーと値の取得、文字列のカラム取得
備考: スレッドセーフな strtok_r() を推奨

大学のある講義で教授がこのように諭していました。
「皆さんもプログラムを書くときは、再入可能(リエントラント)なプログラムを書くように努めなさい。」

当時は再入可能なプログラムを作るのが良いんだなと言葉だけを捉えて、それが具体的にどういうコードか理解していませんでした。それから10年経て、ようやく再入可能なプログラムを書くようになりました(^ ^;;

先ずは、再入可能ではない strtok() を使用してみます。本当はマルチスレッドなサンプルプログラムを書けば良いのですが、私が書けないので言わんとする事だけを確認してみます。

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

int main(void) {

    char *s1 = "key:value:recital";
    char *s2 = "name,address,tell";
    char *delimit = "\\/:;=-,.";
    char *buf1, *buf2;
    char *token1, *token2;

    if((buf1 = strdup(s1)) == NULL || 
       (buf2 = strdup(s2)) == NULL) {
        perror("strdup");
        return EXIT_FAILURE;
    }

    for(token1 = strtok(buf1, delimit);
        token1 != NULL;
        token1 = strtok(NULL, delimit)) {
        printf("token1: %s\n", token1);
        token2 = strtok(buf2, delimit);
        printf("token2: %s\n", token2);
    }

    free(buf1);
    free(buf2);
    return EXIT_SUCCESS;
}

実行結果。

token1: key
token2: name
token1: address
token2: name

s2 を分割するために strtok() を呼び出すと、s1 の分割されたサブ文字列へのポインタを上書きしてしまいます。マルチスレッドなプログラムだと、タイミングによって、正常に処理されない可能性があります。

次に strtok_r() を使用します。分割されたサブ文字列へのポインタを引数として渡すことで、文字列毎に個別のポインタを確保します。

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

int main(void) {

    char *s1 = "key:value:recital";
    char *s2 = "name,address,tell";
    char *delimit = "\\/:;=-,.";
    char *buf1, *buf2;
    char *token1, *saveptr1;
    char *token2, *saveptr2;

    if((buf1 = strdup(s1)) == NULL || 
       (buf2 = strdup(s2)) == NULL) {
        perror("strdup");
        return EXIT_FAILURE;
    }

    for(token1 = strtok_r(buf1, delimit, &saveptr1);
        token1 != NULL;
        token1 = strtok_r(NULL, delimit, &saveptr1)) {
        printf("token1: %s\n", token1);
        token2 = strtok_r(buf2, delimit, &saveptr2);
        printf("token2: %s\n", token2);
    }

    free(buf1);
    free(buf2);
    return EXIT_SUCCESS;
}

実行結果。

token1: key
token2: name
token1: value
token2: name
token1: recital
token2: name

strtok_r() の "_r" とは reentrant の意味です。関数によっては、明示的に REENTRANT なマクロ定数を定義させる仕様になっているものもあります。

$ vi /usr/include/unistd.h
extern char *getlogin (void);
#if defined __USE_REENTRANT || defined __USE_POSIX199506
/* Return at most NAME_LEN characters of the login name of the user in NAME.   If it cannot be determined or some other error occurred, return the error
   code.  Otherwise return 0.
   This function is a possible cancellation points and therefore not
   marked with __THROW.  */
extern int getlogin_r (char *__name, size_t __name_len) __nonnull ((1));
#endif

リファレンス:
リエントラント
man 3 strtok