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

分配指针数组后释放内存仍有残留,求原因及恢复方法

堆内存释放后残留问题的分析与解决

问题描述

我在个人项目中遇到的问题简化复现:尝试创建堆上的指针数组,每个指针指向堆分配的结构体或int类型数据。通过任务管理器追踪程序内存占用,发现释放所有内存后,内存占用未回到初始水平,存在残留,且该残留随数组大小近似线性增长。

操作前内存占用0.3MB,完成所有释放操作后仍占用11.2MB;用int类型实验也存在残留,仅比结构体情况略少。

结构体分配示例代码

#include <stdio.h>
#include <stdlib.h>

// 示例结构体
typedef struct dog {
  int Age;
  int Weight;
} Dog;

typedef Dog* DGPtr;

// 创建堆分配的结构体实例
DGPtr CreateDog(int age, int weight){
  DGPtr Ptr = (DGPtr) malloc(sizeof(Dog));
  Ptr->Age = age;
  Ptr->Weight = weight;
  return Ptr;
}

int main(){
  // 输入暂停以便通过任务管理器查看内存状态
  char inp[10];
  int size = 100000000;

  DGPtr myPoint = CreateDog(5,5);

  // Step 1: 分配数组但未写入
  scanf("%s", inp);
  DGPtr* ptrArr = malloc(sizeof(DGPtr) * size);

  // Step 2: 为数组中每个指针分配结构体并写入数据
  scanf("%s", inp);
  for (int i = 0; i < size; i++){
    ptrArr[i] = (DGPtr) malloc(sizeof(Dog) * 1);
    ptrArr[i]->Age = 7;
    ptrArr[i]->Weight = 7;
  }

  // Step 3: 释放数组中每个结构体指针
  scanf("%s", inp);
  for (int i = 0; i < size; i++){
    free(ptrArr[i]);
  }

  // Step 4: 释放指针数组本身
  scanf("%s", inp);
  free(ptrArr);

  scanf("%s", inp);
  return 0;
}

基本类型分配示例代码

int main(){
  char inp[10];
  int size = 100000000;

  // Step 1: 分配数组但未写入
  scanf("%s", inp);
  int** ptrArr = malloc(sizeof(int *) * size);

  // Step 2: 为数组中每个指针分配int内存
  scanf("%s", inp);
  for (int i = 0; i < size; i++){
    ptrArr[i] = malloc(sizeof(int));
  }

  // Step 3: 释放数组中每个int指针
  scanf("%s", inp);
  for (int i = 0; i < size; i++){
    free(ptrArr[i]);
  }

  // Step 4: 释放指针数组本身
  scanf("%s", inp);
  free(ptrArr);

  scanf("%s", inp);
  return 0;
}

内存残留的原因

  • C标准库内存分配器的缓存策略:调用free只是标记内存为可用,不会立刻归还操作系统。分配器会保留这些空闲内存,用于后续malloc请求,避免频繁和操作系统交互的开销。你分配了上亿个极小内存块,释放后这些小块会被分配器用链表等结构管理,管理结构本身的开销加上未归还的空闲内存,就形成了残留。
  • 操作系统内存页管理:操作系统以页(通常4KB)为单位管理内存。大量小内存块分散在多个页中,释放后很难让整个页完全空闲,操作系统不会回收非完全空闲的页,导致这些页的内存仍被统计为进程占用。
  • 任务管理器的统计逻辑:任务管理器显示的是进程的工作集或虚拟内存占用,包含了分配器持有的空闲内存——这部分内存属于进程,但实际上已经可以被程序重新使用。

恢复初始内存占用的方法

  • 调用平台特定的内存回收函数:Windows下可以用_heapmin()(需包含<malloc.h>),它会尝试将堆中的空闲内存归还给操作系统。示例:在free(ptrArr)后添加_heapmin();。注意这是微软专属函数,不跨平台。
  • 避免分配大量小内存块:改为分配连续的大块内存,比如用一个数组存储所有Dog实例,而非每个实例单独malloc。这样释放时一次性归还大块内存,更容易被操作系统回收。示例:
    // 分配连续内存存储所有Dog
    Dog* dogArr = malloc(sizeof(Dog) * size);
    // 使用时直接通过索引访问:dogArr[i].Age = 7;
    // 释放只需一次free(dogArr);
    
  • 终止进程:最彻底的方式是结束进程,操作系统会回收该进程占用的所有内存。如果程序是一次性任务,完成后退出即可回到初始内存状态。

内容的提问来源于stack exchange,提问作者Manlx

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 19:24:55