Tokenisation strtok() strtok r() 和 strtok s()

函式 strtok 使用一組分隔符將字串分解為較小的字串或標記。

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

int main(void)
{
    int toknum = 0;
    char src[] = "Hello,, world!";
    const char delimiters[] = ", !";
    char *token = strtok(src, delimiters);
    while (token != NULL)
    {
        printf("%d: [%s]\n", ++toknum, token);
        token = strtok(NULL, delimiters);
    }
    /* source is now "Hello\0, world\0\0" */
}

輸出:

1: [Hello]
2: [world]

分隔符字串可以包含一個或多個分隔符,並且每次呼叫 strtok 時可以使用不同的分隔符字串。

呼叫 strtok 繼續標記相同的源字串不應再次傳遞源字串,而是將 NULL 作為第一個引數傳遞。如果傳遞相同的源字串*,*則第一個令牌將被重新標記化。也就是說,給定相同的分隔符,strtok 將再次返回第一個令牌。

請注意,由於 strtok 不為標記分配新記憶體,因此會修改源字串。也就是說,在上面的例子中,字串 src 將被操作以產生由呼叫 strtok 返回的指標引用的標記。這意味著源字串不能是 const(因此它不能是字串文字)。它還意味著分隔位元組的標識丟失(即在示例中,“,”和“!”被有效地從源字串中刪除,你無法分辨哪個分隔符匹配)。

另請注意,源字串中的多個連續分隔符被視為一個; 在示例中,第二個逗號被忽略。

strtok 既不是執行緒安全也不是重入,因為它在解析時使用靜態緩衝區。這意味著如果一個函式呼叫 strtok,它在使用 strtok 時沒有呼叫的函式也可以使用 strtok,並且任何本身使用 strtok 的函式都無法呼叫它。

舉例說明 strtok 不可重入的問題如下:

char src[] = "1.2,3.5,4.2";
char *first = strtok(src, ","); 

do 
{
    char *part;
    /* Nested calls to strtok do not work as desired */
    printf("[%s]\n", first);
    part = strtok(first, ".");
    while (part != NULL)
    {
        printf(" [%s]\n", part);
        part = strtok(NULL, ".");
    }
} while ((first = strtok(NULL, ",")) != NULL);

輸出:

[1.2]
 [1]
 [2]

預期的操作是外部 do while 迴圈應該建立由每個十進位制數字字串(1.23.54.2)組成的三個標記,對於每個標記,strtok 呼叫內部迴圈應該將它分成單獨的數字字串(123542)。

但是,因為 strtok 不是可重入的,所以不會發生這種情況。相反,第一個 strtok 正確地建立了“1.2 \ 0”標記,並且內部迴圈正確地建立了標記 12。但是外迴圈中的 strtok 位於內迴圈使用的字串的末尾,並立即返回 NULL。src 陣列的第二個和第三個子字串根本沒有被分析。

Version < C11

標準 C 庫不包含執行緒安全或可重入版本,但其他一些版本不包含,例如 POSIX’strtok_r 。請注意,在 MSVC 上,strtok 等效,strtok_s 是執行緒安全的。

Version >= C11

C11 有一個可選部分,附件 K,提供了一個名為 strtok_s 的執行緒安全和可重入版本。你可以使用 __STDC_LIB_EXT1__ 測試該功能。此可選部分未得到廣泛支援。

strtok_s 函式與 POSIX strtok_r 函式的不同之處在於防止在被標記化的字串之外儲存,並通過檢查執行時約束。但是,在正確編寫的程式中,strtok_sstrtok_r 的行為相同。

現在使用 strtok_s 與示例產生正確的響應,如下所示:

/* you have to announce that you want to use Annex K */ 
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>

#ifndef __STDC_LIB_EXT1__
# error "we need strtok_s from Annex K"
#endif

char src[] = "1.2,3.5,4.2";  
char *next = NULL;
char *first = strtok_s(src, ",", &next);

do 
{
    char *part;
    char *posn;

    printf("[%s]\n", first);
    part = strtok_s(first, ".", &posn);
    while (part != NULL)
    {
        printf(" [%s]\n", part);
        part = strtok_s(NULL, ".", &posn);
    }
} 
while ((first = strtok_s(NULL, ",", &next)) != NULL);

輸出將是:

[1.2]
 [1]
 [2]
[3.5]
 [3]
 [5]
[4.2]
 [4]
 [2]