C语言ft_split函数内存分配释放错误与段错误问题求助
函数要求
使用malloc分配内存,返回以字符
c为分隔符分割字符串s得到的字符串数组,数组必须以NULL指针结尾。
现存问题
- 输入末尾带分隔符的测试字符串(
" To be or not to be that is the question ")时触发段错误(Segmentation fault) - 输入测试字符串
"Hello there"时,虽然能输出正确分割结果,但输出完成后同样触发段错误,输出表现为:
Hello
there
Segmentation fault
- 不清楚如何正确释放函数内分配的内存
原实现代码
size_t ft_strlen(const char *s) { size_t i; i = 0; while (s[i] != '\0') { i++; } return (i); } char *ft_strcpy(char *dest, const char *src) { int i; i = 0; while (src[i] != '\0') { dest[i] = src[i]; i++; } dest[i] = '\0'; return (dest); } char *string_length(char const *str, char c, int i) { int j; char *string; j = 0; // 调试第一个测试字符串时,断点直接触发在这行 string = (char *)malloc((ft_strlen(str)) * sizeof(*string)); if(!string) return (NULL); while (str[i] == c) i++; while (str[i]) { *string = str[i]; if (str[i + 1] == c || str[i + 1] == '\0') { string++; j++; *string = '\0'; return (string - j); } string++; j++; i++; } return (0); } int count_strings(char const *str, char c) { int i; int count; int def; i = 0; count = 0; def = 1; while (str[i] == c) i++; while (str[i]) { if (str[i] == c && def == 1) { i++; def = 0; } if (str[i] != c && def == 0) { count++; def = 1; } i++; } return (count); } int first_del(char const *str, char c, int i) { while (str[i] == c) i++; return (i); } char **ft_split(char const *s, char c) { int count; int i; int len; int num_of_strings; char **split; char *string; char **it; i = 0; count = 0; num_of_strings = count_strings(s, c); split = (char **)malloc((num_of_strings + 1) * sizeof(char *)); if (!split) return (NULL); split[num_of_strings + 1] = NULL; while (i <= num_of_strings) { count = first_del(s, c, count); string = string_length(s, c, count); len = ft_strlen(string); split[i] = (char *)malloc(len + 1); ft_strcpy(split[i], string); count += len; i++; free(string); string = NULL; } return (split); } int main(void) { char s[] = " To be or not to be that is the question "; char **split_strings = ft_split(s, ' '); for (int i = 0; i < 10; i++) printf("%s\n", split_strings[i]); char s2[] = "Hello there"; char **split_strings2 = ft_split(s2, ' '); for (int i = 0; i < 2; i++) printf("%s\n", split_strings2[i]); }
问题根因与修复
1. 字符串计数逻辑错误
count_strings函数返回值比实际分割结果少1:初始跳过前导分隔符后没有统计第一个有效字符串,只有遇到分隔符后再碰到非分隔符才计数,比如"Hello there"实际有2个分割结果,函数只会返回1。
修复逻辑:跳过前导分隔符后如果没到字符串结尾,先给计数加1;后续遍历遇到连续分隔符时直接跳过全部,跳过后还有有效字符则计数加1。
int count_strings(char const *str, char c) { int i = 0; int count = 0; // 跳过前导分隔符 while (str[i] == c) i++; // 第一个有效字符串直接计数 if (str[i] != '\0') count++; while (str[i]) { if (str[i] == c) { // 跳过所有连续分隔符 while (str[i] == c) i++; // 跳过后还有有效字符则计数+1 if (str[i] != '\0') count++; continue; } i++; } return count; }
2. NULL终止位越界写入
给split数组分配了num_of_strings + 1个元素(索引范围0到num_of_strings),但原代码写的是split[num_of_strings + 1] = NULL;,写入位置超出了已分配内存的范围,属于越界写,是段错误的核心诱因。
修复:改为split[num_of_strings] = NULL;
3. 循环次数多跑一轮
原循环条件为i <= num_of_strings,当i等于num_of_strings时,已经到了要存储NULL的终止位,此时还调用string_length取子串,会访问字符串结束后的非法内存,触发段错误。
修复:循环条件改为i < num_of_strings,循环结束后再手动设置终止位NULL。
4. 临时子串逻辑冗余+空指针风险
string_length函数每次分配和原串等长的内存存临时子串,完全没有必要;且当遍历到字符串末尾无有效子串时会返回NULL,后续传给ft_strlen会直接触发空指针访问错误。
修复:去掉临时子串的分配逻辑,直接从当前位置计算子串长度即可,省掉不必要的malloc/free开销,也避免空指针问题。
5. 内存释放逻辑错误
ft_split返回的是二级指针,释放时需要先遍历数组,逐个释放每个分割出来的字符串(一级指针),最后再释放二级指针本身。原代码在ft_split内部释放临时字符串的逻辑是对的,但main函数中没有释放最终返回的数组,会造成内存泄漏。
正确释放示例:
int j = 0; while (split_strings[j]) { free(split_strings[j]); j++; } free(split_strings);
6. main函数遍历越界
原代码写死循环10次、2次打印结果,一旦实际分割数量和写死的值不匹配,就会访问越界内存触发段错误。
修复:遍历到数组元素为NULL时停止即可:
for (int i = 0; split_strings[i] != NULL; i++) printf("%s\n", split_strings[i]);
修复后完整可运行版本
#include <stdlib.h> #include <stdio.h> size_t ft_strlen(const char *s) { size_t i = 0; while (s[i] != '\0') i++; return (i); } char *ft_strncpy(char *dest, const char *src, unsigned int n) { unsigned int i = 0; while (i < n && src[i] != '\0') { dest[i] = src[i]; i++; } while (i < n) { dest[i] = '\0'; i++; } return (dest); } int count_strings(char const *str, char c) { int i = 0; int count = 0; while (str[i] == c) i++; if (str[i] != '\0') count++; while (str[i]) { if (str[i] == c) { while (str[i] == c) i++; if (str[i] != '\0') count++; continue; } i++; } return count; } int first_del(char const *str, char c, int i) { while (str[i] == c) i++; return (i); } char **ft_split(char const *s, char c) { int i = 0; int pos = 0; int sub_len; int num_of_strings; char **split; num_of_strings = count_strings(s, c); split = (char **)malloc((num_of_strings + 1) * sizeof(char *)); if (!split) return (NULL); pos = first_del(s, c, pos); while (i < num_of_strings) { sub_len = 0; while (s[pos + sub_len] && s[pos + sub_len] != c) sub_len++; split[i] = (char *)malloc(sub_len + 1); if (!split[i]) return (NULL); ft_strncpy(split[i], s + pos, sub_len); split[i][sub_len] = '\0'; pos += sub_len; pos = first_del(s, c, pos); i++; } split[num_of_strings] = NULL; return (split); } int main(void) { char s[] = " To be or not to be that is the question "; char **split_strings = ft_split(s, ' '); for (int i = 0; split_strings[i] != NULL; i++) printf("%s\n", split_strings[i]); // 释放内存 int j = 0; while (split_strings[j]) { free(split_strings[j]); j++; } free(split_strings); printf("--------\n"); char s2[] = "Hello there"; char **split_strings2 = ft_split(s2, ' '); for (int i = 0; split_strings2[i] != NULL; i++) printf("%s\n", split_strings2[i]); // 释放内存 j = 0; while (split_strings2[j]) { free(split_strings2[j]); j++; } free(split_strings2); return 0; }
内容的提问来源于stack exchange,提问作者Nizz

