You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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;
}

错误原因分析
  1. 参数类型不匹配&赋值错误:
    • 函数参数char **string是二级指针,但main中传入的testline是一级指针(数组名),类型完全不匹配。更严重的是,str = string直接将二级指针赋值给一级指针str,导致指针指向非法内存,触发访问异常。
  2. NULL参数处理逻辑错误:
    • 传入NULL(第二次调用)时直接返回NULL,违背了类似strtok的设计逻辑——传入NULL应该继续处理上一次未完成的字符串,而非直接返回空。
  3. 未更新剩余字符串指针:
    • 仅将分隔符位置设为\0得到当前token,但未将静态指针str移动到分隔符的下一个位置,导致后续调用无法定位剩余字符串。
  4. 遍历逻辑缺陷:
    • 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 23:20:57