自定义strtok函数如何返回剩余字符串?解决调用异常问题
问题描述
我自己实现了一个类似strtok的函数mystringtokenize,想要实现返回当前分隔出的token,同时将剩余未处理的字符串通过输出参数返回的功能。但调用时出现异常:比如处理"Hello, World"时,首次调用返回"Hello",再次传入NULL调用时触发异常。
我的代码
自定义的mystringtokenize函数
char *mystringtokenize(char **string, char delimiter) { static char *str = NULL; int stringIndex = 0; if (string != NULL) { //check if string is NULL, if its not null set str to string str = string; } if (string == NULL) { //return NULL if string is empty return NULL; } do { //traverse through string if (!str[stringIndex]) { //if str at string index is null character, stop while loop break; } stringIndex++;//index through string } while (str[stringIndex] != delimiter); str[stringIndex] = '\0'; //cut the string char *lastToken = str; //set last token to the cut off part return lastToken; }
main函数调用代码
int main(int argc, char const *argv[]) { FILE *inputStream = fopen("FitBitData.csv", "r"); int index = 0; int fitbitindex = 0; char testline[100]; char minute[10] = "0:00:00"; FitbitData fitBitUser[1446]; if (inputStream != NULL) { while (fgets(testline, sizeof(testline), inputStream) != NULL) { strcpy(fitBitUser[fitbitindex].patient, mystringtokenize(testline, ',')); strcpy(fitBitUser[fitbitindex].minute, mystringtokenize(NULL, ',')); printf("%s %s\n", fitBitUser[fitbitindex].patient, fitBitUser[fitbitindex].minute); printf("%s\n", fitBitUser[fitbitindex].patient); fitbitindex++; } } return 0; }
错误原因分析
- 参数类型不匹配&赋值错误:
- 函数参数
char **string是二级指针,但main中传入的testline是一级指针(数组名),类型完全不匹配。更严重的是,str = string直接将二级指针赋值给一级指针str,导致指针指向非法内存,触发访问异常。
- 函数参数
- NULL参数处理逻辑错误:
- 传入
NULL(第二次调用)时直接返回NULL,违背了类似strtok的设计逻辑——传入NULL应该继续处理上一次未完成的字符串,而非直接返回空。
- 传入
- 未更新剩余字符串指针:
- 仅将分隔符位置设为
\0得到当前token,但未将静态指针str移动到分隔符的下一个位置,导致后续调用无法定位剩余字符串。
- 仅将分隔符位置设为
- 遍历逻辑缺陷:
do-while循环顺序错误:先判断当前字符是否为\0,再自增索引,最后判断是否为分隔符。这会跳过第一个字符的检查,且可能出现越界访问。
修正后的代码
根据需求,提供两种实现方案:
方案1:输出参数返回剩余字符串(线程安全)
直接通过输出参数返回剩余未处理的字符串,由调用者管理状态,无静态变量,线程安全:
// 返回当前分隔出的token,同时将剩余未处理字符串通过*rest输出 char *mystringtokenize(char *string, char delimiter, char **rest) { char *start = string; // 传入空字符串直接返回NULL if (!start) { if (rest) *rest = NULL; return NULL; } // 跳过开头的连续分隔符(可选,按需调整) while (*start == delimiter) { start++; } // 已到字符串末尾,返回NULL if (!*start) { if (rest) *rest = NULL; return NULL; } // 寻找下一个分隔符的位置 char *end = start; while (*end && *end != delimiter) { end++; } // 处理分隔符,设置剩余字符串指针 if (*end == delimiter) { *end = '\0'; if (rest) *rest = end + 1; } else { // 到字符串末尾,剩余字符串为NULL if (rest) *rest = NULL; } return start; }
方案2:兼容strtok调用方式(静态变量版)
用静态变量保存剩余字符串,支持首次传目标字符串、后续传NULL的调用方式:
char *mystringtokenize(char *string, char delimiter) { static char *remaining = NULL; // 传入新字符串时更新剩余指针 if (string) { remaining = string; } // 无剩余字符串时返回NULL if (!remaining) { return NULL; } // 跳过开头的连续分隔符 while (*remaining == delimiter) { remaining++; } if (!*remaining) { remaining = NULL; return NULL; } char *token_start = remaining; // 寻找下一个分隔符 while (*remaining && *remaining != delimiter) { remaining++; } // 更新剩余指针到分隔符之后 if (*remaining == delimiter) { *remaining = '\0'; remaining++; } else { remaining = NULL; // 处理完毕,后续调用返回NULL } return token_start; }
修正后的main调用(针对方案2)
int main(int argc, char const *argv[]) { FILE *inputStream = fopen("FitBitData.csv", "r"); int fitbitindex = 0; char testline[100]; FitbitData fitBitUser[1446]; if (inputStream != NULL) { while (fgets(testline, sizeof(testline), inputStream) != NULL) { // 去掉fgets读取的换行符 testline[strcspn(testline, "\n")] = '\0'; // 第一次调用传入目标字符串 char *patient = mystringtokenize(testline, ','); if (patient) { // 用strncpy避免数组溢出,手动加结束符 strncpy(fitBitUser[fitbitindex].patient, patient, sizeof(fitBitUser[fitbitindex].patient)-1); fitBitUser[fitbitindex].patient[sizeof(fitBitUser[fitbitindex].patient)-1] = '\0'; } // 第二次调用传NULL,继续处理剩余字符串 char *minute = mystringtokenize(NULL, ','); if (minute) { strncpy(fitBitUser[fitbitindex].minute, minute, sizeof(fitBitUser[fitbitindex].minute)-1); fitBitUser[fitbitindex].minute[sizeof(fitBitUser[fitbitindex].minute)-1] = '\0'; } printf("%s %s\n", fitBitUser[fitbitindex].patient, fitBitUser[fitbitindex].minute); fitbitindex++; } fclose(inputStream); // 记得关闭文件 } return 0; }
额外注意事项
- 避免使用
strcpy直接赋值,建议用strncpy并手动添加字符串结束符,防止目标数组溢出。 fgets会读取换行符,需用strcspn提前去除,避免token包含无效换行符。- 静态变量版本的函数不是线程安全的,多线程场景建议使用方案1。
内容的提问来源于stack exchange,提问作者sangregoriokimpo
相关产品推荐
相关产品推荐

