C语言字符串多'\0'的含义及释放首个'\0'后内存的方法
你编写的极简空白字符修剪函数代码如下:
char *trim(char *line) { char *out = strdup(line); // copy valid chars to duplicate char *op = out; char *ip = line; while (*ip != '\0') { if (!isspace(*ip)) { *op = *ip; op++; } ip++; } // put second '\0' in string, although more memory is dedicated to it *op = '\0'; return out; }
调用该函数处理类似{'F', ' ', 'o', ' ', ' ', 'o', '\0'}的字符串时,返回的内存块内容为{'F', 'o', 'o', '\0', ' ', 'o', '\0'},无法得到仅包含{'F', 'o', 'o', '\0'}的紧凑内存块,以下是针对你提出的两个问题的解答:
问题1:字符串中存在多个'\0'意味着什么?使用strdup复制后strlen检查长度不符合预期的原因?
在C语言中,标准字符串是以首个'\0'作为唯一结束标志,后续的'\0'或其他字节都不属于该字符串的逻辑内容——标准字符串处理函数(如strlen、strcpy、strcmp等)都会在遇到第一个'\0'时停止操作。
你用strdup复制原字符串时,strdup会根据原字符串首个'\0'的位置计算长度,分配「原字符串长度+1」的内存,再把原字符串的所有字节(直到首个'\0')复制过去。而你的修剪操作在新内存块中提前写入了一个'\0',导致内存块里出现两个'\0':第一个是修剪后字符串的结束标志,第二个是原字符串末尾的'\0'残留。
strlen的结果是符合逻辑的——它只会统计到第一个'\0'之前的字符数;你看到的内存中后续字节,只是strdup分配的多余内存里保留的原字符串残留数据,不属于当前逻辑字符串的一部分。
问题2:如何释放首个'\0'之后的内存,使字符串仅占用必要空间?
要让返回的字符串仅占用有效内容所需的内存,有两种可行方案:
方案1:先统计长度再分配刚好的内存
先遍历原字符串统计非空白字符的数量,直接分配「数量+1」的内存(+1是为了存结束符'\0'),再复制有效字符,从根源避免多余内存分配:
#include <stdlib.h> #include <ctype.h> #include <string.h> char *trim(char *line) { // 统计非空白字符的数量 int valid_count = 0; char *ip = line; while (*ip != '\0') { // 用unsigned char避免isspace处理负数字符的未定义行为 if (!isspace((unsigned char)*ip)) { valid_count++; } ip++; } // 分配刚好容纳有效字符+结束符的内存 char *out = malloc(valid_count + 1); if (out == NULL) { // 必须检查内存分配失败的情况 return NULL; } // 复制非空白字符到新内存 char *op = out; ip = line; while (*ip != '\0') { if (!isspace((unsigned char)*ip)) { *op = *ip; op++; } ip++; } *op = '\0'; // 添加结束符 return out; }
方案2:用realloc收缩已分配的内存
如果坚持先用strdup复制原字符串,可以在修剪完成后,用realloc函数重新分配内存到有效内容的大小:
#include <stdlib.h> #include <ctype.h> #include <string.h> char *trim(char *line) { char *out = strdup(line); if (out == NULL) { return NULL; } char *op = out; char *ip = line; while (*ip != '\0') { if (!isspace((unsigned char)*ip)) { *op = *ip; op++; } ip++; } *op = '\0'; // 计算有效内容所需的内存大小(包括结束符) size_t new_size = op - out + 1; // 重新分配内存,成功则更新指针,失败则保留原内存(可根据需求添加错误处理) char *shrunk_out = realloc(out, new_size); if (shrunk_out != NULL) { out = shrunk_out; } return out; }
注意:无论使用哪种方案,调用trim函数的代码都必须用free()释放返回的指针,避免内存泄漏。
内容的提问来源于stack exchange,提问作者sueszli

