C语言字符串操作函数实现的安全性及内存释放问题咨询
字符串操作函数的安全问题与内存管理疑问
实现代码
#include <stdio.h> #include <string.h> #include <stdlib.h> char *char_repeater(char *str, char ch) { int tmp_len = strlen(str) + 1; // 初始tmp大小 char *tmp = (char *)malloc(tmp_len); // 分配初始内存 // 实际场景中通常难以提前计算最终长度 int j = 0; for (int i = 0; i < strlen(str); i++) { tmp[j] = str[i]; j++; if (str[i] == ch) { tmp[j] = str[i]; j++; } if (j > tmp_len) { tmp_len *= 2; // 扩容因子 tmp = realloc(tmp, tmp_len); } } tmp[j] = 0; char *output = (char *)malloc(strlen(tmp) + 1); // 分配与最终字符串长度匹配的内存 strncpy(output, tmp, strlen(tmp)); output[strlen(tmp)] = 0; free(tmp); // 是否有必要? return output; } int main() { char *str = "This is a test"; str = char_repeater(str, 'i'); puts(str); free(str); return 0; }
技术疑问
- 该实现方案整体是否安全?
- 当前实际是将新数据写入原指针,若output长度大于原str会覆盖旧数据,若更短则旧数据残留导致内存泄漏,如何在函数内先free原str再将输出赋值给该指针?
问题1:实现方案的安全性分析
这个实现存在多处不安全的问题,具体如下:
- 字符串字面量非法释放:main函数中
char *str = "This is a test";指向的是只读存储区的字符串字面量,后续调用free(str)属于非法操作,会触发未定义行为。只有通过malloc/calloc/realloc分配的内存才能用free释放。 - 数组越界风险:扩容判断条件
if (j > tmp_len)错误。tmp的大小是tmp_len,合法索引范围是0到tmp_len-1,当j == tmp_len时,写入tmp[j]已经越界,应该改成if (j >= tmp_len),否则会破坏堆结构,引发程序崩溃或异常。 - 未检查内存分配返回值:
malloc和realloc在内存不足时会返回NULL,代码中直接使用返回的指针,一旦分配失败会触发空指针解引用,导致程序崩溃。 - 冗余内存分配与拷贝:最后分配
output并拷贝tmp的操作完全多余,直接调用tmp = realloc(tmp, strlen(tmp)+1)调整tmp到实际需要的大小,然后返回tmp即可,既节省内存又避免多余拷贝。 - 低效的
strlen调用:for循环条件中每次都调用strlen(str),会反复遍历整个字符串,降低效率,应该提前把strlen(str)的值存入变量,循环中直接使用该变量。 - 关于
free(tmp):如果保留output的分配步骤,free(tmp)是必须的,否则tmp指向的内存会泄漏;但如果优化掉output,直接返回调整后的tmp,就不需要这一步。
问题2:函数内释放原指针并赋值新内存的实现方法
要实现函数内释放原字符串内存并赋值新内容,需要注意以下两点:
- 原字符串必须是动态分配的:不能是字符串字面量,否则
free会触发未定义行为,调用函数前要确保传入的指针是malloc/calloc/realloc分配的。 - 使用二级指针传参:因为要修改传入的指针本身,需要把参数改为
char **str_ptr,这样函数内部可以直接操作原指针的地址。
修改后的示例代码如下:
#include <stdio.h> #include <string.h> #include <stdlib.h> // 返回0表示成功,-1表示内存分配失败 int char_repeater(char **str_ptr, char ch) { if (str_ptr == NULL || *str_ptr == NULL) return -1; char *str = *str_ptr; int original_len = strlen(str); int tmp_len = original_len + 1; char *tmp = malloc(tmp_len); if (tmp == NULL) return -1; int j = 0; for (int i = 0; i < original_len; i++) { tmp[j] = str[i]; j++; if (str[i] == ch) { tmp[j] = str[i]; j++; } // 提前检查,避免越界 if (j >= tmp_len) { tmp_len *= 2; char *new_tmp = realloc(tmp, tmp_len); if (new_tmp == NULL) { free(tmp); return -1; } tmp = new_tmp; } } tmp[j] = '\0'; // 调整到实际需要的大小 char *final_str = realloc(tmp, j + 1); if (final_str == NULL) { free(tmp); return -1; } // 释放原字符串内存 free(*str_ptr); // 赋值新字符串指针 *str_ptr = final_str; return 0; } int main() { // 必须用动态分配的字符串 char *str = strdup("This is a test"); if (str == NULL) return 1; if (char_repeater(&str, 'i') == 0) { puts(str); } free(str); return 0; }
这个修改后的版本:
- 通过二级指针
char **str_ptr传入原指针的地址,函数内可以直接释放原内存并修改指针指向。 - 增加了内存分配失败的错误处理,避免内存泄漏或崩溃。
- 提前计算原字符串长度,优化循环效率。
- 调整扩容判断条件,避免数组越界。
- 最后将
tmp调整到实际需要的大小,节省内存。
内容的提问来源于stack exchange,提问作者Googlebot
相关产品推荐
相关产品推荐

