C语言提取子字符串:如何从含空字符的字符串拆分指定子串
从含空字符的字节序列中提取子串并生成字符串数组
问题描述
如何从类似\0\0bon\0\0jour\0的字节序列中提取出"bon"和"jour",并整理成[0] = bon、[1] = jour、[2] = NULL(数组结束标记)的字符串数组?
原尝试代码
#include <stdio.h> #include <stdlib.h> #include <string.h> char **ft_split(char const *s, char c) { char *s1; char *tmp; char **res; int i; size_t len = 100 /* how can I guess the size ? */; s1 = strdup(s); res = malloc(len + 1); if (!res) return (res); i = 0; while (*s1) { tmp = strchr(s1, c) + 1; if (!tmp) break ; if (*tmp) res[i++] = strdup(tmp); s1 = tmp + 1; } res[i] = 0; return (res); } int main(void) { char **res = ft_split("\0bon\0jour", '\0'); int i = 0; while (res[i]) printf("%s\n", res[i++]); i = 0; while (res[i]) free(res[i++]); free(res); return (0); }
原代码存在的问题
strdup无法处理含空字符的序列:strdup会在遇到第一个\0时停止复制,原输入"\0bon\0jour"只会被复制为空字符串,后续处理完全失效。- 硬编码数组长度不合理:
len=100是固定值,无法适配实际子串数量,要么浪费内存,要么内存不足。 - 字符串函数的局限性:
strchr、while(*s1)这类操作都以\0为结束标志,无法遍历到序列中\0之后的内容。 - 非法访问风险:如果
strchr返回NULL,tmp = strchr(...) +1会导致指针越界。
修正方案
要处理含\0的字节序列,必须明确序列的总长度(因为常规字符串函数无法自动获取),分两步完成:先统计有效子串数量,再分配内存并提取子串。
修正后的代码
#include <stdio.h> #include <stdlib.h> #include <string.h> char **ft_split(const unsigned char *s, char c, size_t total_len) { char **res; int count = 0; size_t i = 0; // 第一步:统计有效非空子串的数量 while (i < total_len) { // 跳过连续的分隔符 while (i < total_len && s[i] == c) i++; if (i >= total_len) break; // 定位当前子串的结束位置 while (i < total_len && s[i] != c) i++; count++; } // 分配字符串数组内存(+1用于存放NULL结束标记) res = malloc(sizeof(char *) * (count + 1)); if (!res) return NULL; // 第二步:提取每个有效子串 i = 0; int idx = 0; while (i < total_len) { // 跳过连续分隔符 while (i < total_len && s[i] == c) i++; if (i >= total_len) break; size_t start = i; // 找到子串结束位置 while (i < total_len && s[i] != c) i++; // 为当前子串分配内存并复制内容 res[idx] = malloc(sizeof(char) * (i - start + 1)); if (!res[idx]) { // 内存分配失败,释放已分配的资源 while (idx > 0) free(res[--idx]); free(res); return NULL; } strncpy(res[idx], (const char *)&s[start], i - start); res[idx][i - start] = '\0'; // 手动添加字符串结束符 idx++; } res[idx] = NULL; // 标记数组结束 return res; } int main(void) { // 用数组存储字节序列,避免字符串字面量自动截断 unsigned char data[] = {'\0', '\0', 'b', 'o', 'n', '\0', '\0', 'j', 'o', 'u', 'r', '\0'}; size_t data_len = sizeof(data) / sizeof(data[0]); char **res = ft_split(data, '\0', data_len); if (res) { int i = 0; while (res[i]) { printf("[%d] = %s\n", i, res[i]); i++; } // 释放内存,避免泄漏 i = 0; while (res[i]) free(res[i++]); free(res); } return 0; }
代码关键点说明
- 使用数组存储原始序列:避免字符串字面量在第一个
\0处自动截断,确保能访问到所有字节。 - 传入总长度参数:通过
total_len明确要处理的字节数,实现完整遍历。 - 两次遍历逻辑:第一次统计子串数量,确保内存分配精准;第二次提取子串,逐个复制到数组中。
- 内存安全处理:子串分配失败时,释放已分配的所有内存,避免泄漏。
- 手动添加结束符:复制子串后手动添加
\0,确保每个元素都是合法的C字符串。
内容的提问来源于stack exchange,提问作者Alex Rene
相关产品推荐
相关产品推荐

