首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C 語言字符串分割深度解析:strtok vs strtok_r 線程安全與多層分割

     編輯:關於C語言

C 語言字符串分割深度解析:strtok vs strtok_r 線程安全與多層分割

`strtok` 是 C 標準庫字符串分割函數,但因**內部靜態指針保存狀態**,導致**非線程安全**、**不可遞歸/嵌套調用**。本文詳細對比 `strtok` 缺陷與 `strtok_r`(可重入版)優勢,演示多層分割(如 CSV 解析)的正確姿勢,並解析字符串字面量不可修改的坑點。

strtok 簽名與基本用法

char *strtok(char *str, const char *delim);
// 首次調用傳 str,後續調用傳 NULL
// 返回下一個 token 指針,修改原串插入 '\0'

示例:`"Fred,John,Ann"` 分割 `","` → `"Fred"` `"John"` `"Ann"`。

strtok 致命弱點:靜態狀態

內部靜態指針記錄「下次掃描起始位置」。導致:

  1. **非線程安全**:多線程同時調用會互相覆蓋內部指針。
  2. **不可嵌套**:外層循環調用 `strtok`,內層又調用 `strtok`,內層會破壞外層的掃描狀態。

嵌套調用失敗實例

解析 `"Fred male 25,John male 62,Anna female 16"`:
外層按 `","` 分割 → `"Fred male 25"`、`"John male 62"`...
內層按 `" "` 分割每段 → 姓名、性別、年齡。

使用 `strtok` 時,內層循環將外層的靜態指針改寫,導致外層無法繼續,只能得到第一段數據。

strtok_r:可重入版本

char *strtok_r(char *str, const char *delim, char **saveptr);
// saveptr:用戶提供的狀態指針,替代內部靜態指針
// 首次調用傳 str 地址,後續傳 NULL,saveptr 需保持同一變量

正確多層分割代碼

char buffer[] = "Fred male 25,John male 62,Anna female 16";
char *outer_ptr = NULL;
char *inner_ptr = NULL;
char *token;
int idx = 0;
char *tokens[20];

// 外層按逗號分割
while ((token = strtok_r(buffer, ",", &outer_ptr)) != NULL) {
    // 內層按空格分割
    char *field = strtok_r(token, " ", &inner_ptr);
    while (field != NULL) {
        tokens[idx++] = field;
        field = strtok_r(NULL, " ", &inner_ptr);
    }
    tokens[idx++] = "***"; // 分隔標記
    buffer = NULL; // 後續外層調用傳 NULL
}

// 輸出驗證
for (int i = 0; i < idx; i++) printf(">%s<\n", tokens[i]);

輸出:`>Fred< >male< >25< >***< >John< >male< >62< >***< >Anna< >female< >16< >***<` —— 完美解析所有字段。

關鍵差異總結表

特性strtokstrtok_r
線程安全❌ 靜態指針共享✅ 用戶管理 saveptr
嵌套調用❌ 相互干擾✅ 獨立 saveptr 隔離
標準C89/C99POSIX (Linux/Unix),Windows 用 `strtok_s`
原串修改✅ 插入 '\0'✅ 插入 '\0'

重大陷阱:字符串字面量不可修改

char *s = "a,b,c";  // s 指向只讀段 .rodata
strtok(s, ",");     // 嘗試寫入 '\0' → 段錯誤 (SIGSEGV)

// 正確:用數組在棧上分配可修改內存
char s[] = "a,b,c"; // 編譯器複製字面量到棧數組
strtok(s, ",");     // 安全

**鐵律**:傳給 `strtok`/`strtok_r` 的第一個參數**必須是可寫內存**(棧數組、`malloc` 堆內存),**絕不能是字符串字面量**。

Windows 兼容方案:strtok_s

// MSVC: char *strtok_s(char *str, const char *delim, char **context);
// 用法同 strtok_r,參數順序略異
#ifdef _WIN32
#define strtok_r strtok_s
#endif

現代替代方案(C++/C 進階)

  • C++:`std::stringstream` + `getline`、`std::regex`、`boost::split`、C++20 `std::views::split`。
  • C:手寫分割器(指針掃描、不修改原串、返回切片視圖)、第三方庫(如 `cstrtok`、`inih`)。

總結

`strtok` 因歷史包袱存在嚴重缺陷,**生產代碼禁用**。**`strtok_r` 是正統替代者**,通過用戶態 `saveptr` 實現線程安全與嵌套分割。記住三鐵律:1. 用可寫緩衝區 2. 用 `strtok_r` 3. 多層分割各層獨立 `saveptr`。理解其「修改原串插 `\0`」的破壞性行為,避免在需要保留原串場景誤用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved