自定义类strtok函数返回值异常:main中无法打印token
你遇到的核心问题是返回了指向栈上局部数组的指针,这是C语言中典型的未定义行为,具体原因和解决方法如下:
为什么会出现这种现象?
你的tokenizer函数里,char arr[i+1];是在栈帧上分配的局部数组。当函数执行完毕返回时,当前函数的栈帧会被销毁,这块内存会被标记为“可复用”。此时main函数里的p就变成了悬空指针——指向一块已经不属于当前程序有效内存的地址。
至于为什么保留tokenizer里的printf时main能正常打印?这只是巧合:当你在tokenizer内部调用printf时,它没有覆盖arr所在的栈内存区域,返回后这块内存暂时还保留着原来的值。但这种情况完全不可靠,更换编译器、优化等级或者添加其他代码后,结果立刻会变得不可预测。
修复方案
我们需要让返回的指针指向生命周期更长的内存区域,这里提供三种常用的解决思路:
1. 使用静态数组(简单但有局限性)
把局部数组改成静态存储类,这样它的生命周期会持续到程序结束。但要注意静态数组是全局共享的,存在线程不安全、不可重入的问题,且需要预估最大token长度:
char *tokenizer(char s[], const char *delimiter) { static char arr[1024]; // 假设token最大长度不超过1023 int i = 0; // 新增判断:避免字符串无分隔符时越界 while(s[i] != '\0' && s[i] != *delimiter) i++; int j = 0; i = 0; while(s[i] != '\0' && s[i] != *delimiter) { arr[j] = s[i]; i++; j++; } arr[j] = '\0'; return arr; }
2. 动态分配内存(灵活但需手动释放)
用malloc在堆上分配内存,堆内存不会随函数栈帧销毁,需要调用者在使用完后手动释放,避免内存泄漏:
char *tokenizer(char s[], const char *delimiter) { int i = 0; while(s[i] != '\0' && s[i] != *delimiter) i++; // 分配足够存储token和终止符的内存 char *arr = malloc(i + 1); if (arr == NULL) { // 必须检查malloc是否成功 return NULL; } int j = 0; i = 0; while(s[i] != '\0' && s[i] != *delimiter) { arr[j] = s[i]; i++; j++; } arr[j] = '\0'; return arr; }
main函数调用时要记得释放内存:
char s[] = "tab-tab"; const char del[2] = "-"; char *p = tokenizer(s, del); if (p != NULL) { printf("%s\n", p); free(p); // 释放堆内存 }
3. 让调用者提供缓冲区(最安全)
由调用者传入一个缓冲区和缓冲区大小,函数将结果写入该缓冲区,完全避免内存管理问题:
// 返回值:成功返回缓冲区指针,失败返回NULL(缓冲区不足) char *tokenizer(char s[], const char *delimiter, char *buf, size_t buf_size) { int i = 0; while(s[i] != '\0' && s[i] != *delimiter) i++; // 检查缓冲区是否足够存储token和终止符 if (i + 1 > buf_size) { return NULL; } int j = 0; i = 0; while(s[i] != '\0' && s[i] != *delimiter) { buf[j] = s[i]; i++; j++; } buf[j] = '\0'; return buf; }
main函数调用示例:
char s[] = "tab-tab"; const char del[2] = "-"; char buf[1024]; char *p = tokenizer(s, del, buf, sizeof(buf)); if (p != NULL) { printf("%s\n", p); }
额外修复:避免越界访问
你原来的代码还有一个隐藏bug:while(s[i] != *delimiter)没有判断s[i]是否为'\0',如果输入字符串中没有分隔符,会一直循环直到越界访问内存,这同样会导致未定义行为。上面的修复方案都已经补充了s[i] != '\0'的判断,一定要加上。
内容的提问来源于stack exchange,提问作者MkPtr

