You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

技术疑问

  1. 该实现方案整体是否安全?
  2. 当前实际是将新数据写入原指针,若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:函数内释放原指针并赋值新内存的实现方法

要实现函数内释放原字符串内存并赋值新内容,需要注意以下两点:

  1. 原字符串必须是动态分配的:不能是字符串字面量,否则free会触发未定义行为,调用函数前要确保传入的指针是malloc/calloc/realloc分配的。
  2. 使用二级指针传参:因为要修改传入的指针本身,需要把参数改为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 13:45:39