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

Ubuntu下C程序运行时RES内存free后未回落问题求助

问题描述

Ubuntu下程序因top中RES内存持续增长崩溃,编写测试程序跟踪malloc/free对应的RES内存,但结果不符合预期:

测试代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/resource.h>

#define CAPACITY_DEBUG 1000000

void printMemUsage(){
    struct rusage usage;
    getrusage(RUSAGE_SELF, &usage);
    fprintf(stderr,"Memory usage: %ld KB .................\n", usage.ru_maxrss);
}

void MallocTest(){
    fprintf(stderr,"test 1 started ......................!!\n");
    printMemUsage();
    sleep(5);

    fprintf(stderr,"starting to allocate ....................\n");
    int* arr = (int*)malloc(CAPACITY_DEBUG * sizeof(int));
    if (arr == NULL) {
        fprintf(stderr, "Memory allocation failed. Exiting...\n");
        return;
    }
    for(int i = 0; i < CAPACITY_DEBUG; i++){
        arr[i] = 5;
    }
    printMemUsage();
    fprintf(stderr,"allocation done, going to sleep................\n");
    sleep(15);

    fprintf(stderr,"Woke up, starting to free............\n");
    free(arr); // Free the memory when you're done.
    // malloc_trim(0);
    fprintf(stderr,"free done................\n");
    sleep(5);
    printMemUsage();
}

void MallocTest2(){
    fprintf(stderr,"test 2 started ......................!!\n");
    printMemUsage();
    sleep(5);

    fprintf(stderr,"starting to allocate ....................\n");
    int* arr[CAPACITY_DEBUG];  // Declare an array of integer pointers
    for (int i = 0; i < CAPACITY_DEBUG; i++) {
        arr[i] = (int*)malloc(sizeof(int));  // Allocate memory for each element
        if (arr[i] == NULL) {
            // Handle memory allocation failure
            perror("malloc");
            exit(1);
        }
        // Initialize the value in the allocated memory
        *arr[i] = i * 2;
    }
    printMemUsage();
    fprintf(stderr,"allocation done, going to sleep................\n");
    sleep(15);

    fprintf(stderr,"Woke up, starting to free............\n");
    // Don't forget to free the allocated memory when done
    for (int i = 0; i < CAPACITY_DEBUG; i++) {
        free(arr[i]);
    }
    //malloc_trim(0);
    fprintf(stderr,"free done................\n");
    sleep(30);
    printMemUsage();
}

int main(){
    MallocTest2();
    return 1;
}

程序运行输出

$ ./memTest2 
test 2 started ......................!!
Memory usage: 2680 KB .................
starting to allocate ....................
Memory usage: 40560 KB .................
allocation done, going to sleep................
Woke up, starting to free............
free done................
Memory usage: 40560 KB .................

Valgrind运行输出

$ valgrind --leak-check=full --show-leak-kinds=all ./memTest2
==104110== Memcheck, a memory error detector
==104110== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==104110== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==104110== Command: ./memTest2
==104110== 
### unhandled dwarf2 abbrev form code 0x25
### unhandled dwarf2 abbrev form code 0x25
### unhandled dwarf2 abbrev form code 0x25
### unhandled dwarf2 abbrev form code 0x1b
get_Form_szB: unhandled 27 (DW_FORM_addrx)
--104110-- WARNING: Serious error when reading debug info
--104110-- When reading debug info from /home/reve/reve_dpdk_v22.11.1_ubuntu_v22.04.2/projects/fuji_projects/dpdkbasic/memTest2:
--104110-- get_Form_contents: unhandled DW_FORM
==104110== Warning: client switching stacks?  SP change: 0x1fff0002b8 --> 0x1ffe85f020
==104110==          to suppress, use: --max-stackframe=8000152 or greater
==104110== Invalid write of size 8
==104110==    at 0x109345: MallocTest2 (src/media_queue_handler/queueTest.c:83)
==104110==    by 0x10BDE7: ??? (in /home/reve/reve_dpdk_v22.11.1_ubuntu_v22.04.2/projects/fuji_projects/dpdkbasic/memTest2)
==104110==    by 0x1094B5: main (src/media_queue_handler/queueTest.c:116)
==104110==  Address 0x1ffe85f018 is on thread 1's stack
==104110==  in frame #0, created by MallocTest2 (src/media_queue_handler/queueTest.c:82)
==104110== 
test 2 started ......................!!
==104110== Invalid read of size 8
==104110==    at 0x48E8026: fwrite (iofwrite.c:49)
==104110==    by 0x109349: MallocTest2 (src/media_queue_handler/queueTest.c:83)
==104110==    by 0x1094B5: main (src/media_queue_handler/queueTest.c:116)
==104110==  Address 0x1ffe85f018 is on thread 1's stack
==104110==  in frame #0, created by fwrite (iofwrite.c:31)
==104110== 
==104110== Syscall param getrusage(usage) points to unaddressable byte(s)
==104110==    at 0x498254B: getrusage (getrusage.c:29)
==104110==    by 0x10935A: MallocTest2 (src/media_queue_handler/queueTest.c:17)
==104110==    by 0x1094B5: main (src/media_queue_handler/queueTest.c:116)
==104110==  Address 0x1ffe85f0b0 is on thread 1's stack
==104110==  in frame #1, created by MallocTest2 (src/media_queue_handler/queueTest.c:82)
==104110== 
Memory usage: 55336 KB .................
starting to allocate ....................
==104110== Invalid write of size 8
==104110==    at 0x1093AA: MallocTest2 (src/media_queue_handler/queueTest.c:90)
==104110==    by 0x1094B5: main (src/media_queue_handler/queueTest.c:116)
==104110==  Address 0x1ffe85f140 is on thread 1's stack
==104110==  in frame #0, created by MallocTest2 (src/media_queue_handler/queueTest.c:82)
==104110== 
==104110== Syscall param getrusage(usage) points to unaddressable byte(s)
==104110==    at 0x498254B: getrusage (getrusage.c:29)
==104110==    by 0x1093D5: MallocTest2 (src/media_queue_handler/queueTest.c:17)
==104110==    by 0x1094B5: main (src/media_queue_handler/queueTest.c:116)
==104110==  Address 0x1ffe85f020 is on thread 1's stack
==104110==  in frame #1, created by MallocTest2 (src/media_queue_handler/queueTest.c:82)
==104110== 
Memory usage: 204140 KB .................
allocation done, going to sleep................
Woke up, starting to free............
==104110== Invalid read of size 8
==104110==    at 0x109430: MallocTest2 (src/media_queue_handler/queueTest.c:106)
==104110==    by 0x1094B5: main (src/media_queue_handler/queueTest.c:116)
==104110==  Address 0x1ffe85f140 is on thread 1's stack
==104110==  in frame #0, created by MallocTest2 (src/media_queue_handler/queueTest.c:82)
==104110== 
free done................
Memory usage: 204140 KB .................
==104110== Warning: client switching stacks?  SP change: 0x1ffe85f020 --> 0x1fff0002b8
==104110==          to suppress, use: --max-stackframe=8000152 or greater
==104110== 
==104110== HEAP SUMMARY:
==104110==     in use at exit: 0 bytes in 0 blocks
==104110==   total heap usage: 1,000,000 allocs, 1,000,000 frees, 4,000,000 bytes allocated
==104110== 
==104110== All heap blocks were freed -- no leaks are possible
==104110== 
==104110== For lists of detected and suppressed errors, rerun with -s
==104110== ERROR SUMMARY: 1999938 errors from 6 contexts (suppressed: 0 from 0)

预期free后内存回落,但实际内存用量远大于分配量且未回落,Valgrind显示无泄漏但有大量错误,求问题原因及正确跟踪方法。


问题分析与解决方案

1. 内存统计函数用错了,根本没拿到当前RES值

你使用的ru_maxrss是进程启动以来的最大驻留内存峰值,不是当前的RES值!所以不管怎么free,这个值只会上升不会下降,这是你看到free后内存数值不变的核心原因。

要获取当前的驻留内存(对应top的RES),应该读取/proc/self/status中的VmRSS字段,或者/proc/self/statm的第二个数值。修改printMemUsage函数:

#include <string.h>

void printMemUsage(){
    FILE* fp = fopen("/proc/self/status", "r");
    if (!fp) return;
    char line[256];
    while (fgets(line, sizeof(line), fp)) {
        if (strncmp(line, "VmRSS:", 6) == 0) {
            fprintf(stderr, "Current RES: %s", line + 6);
            break;
        }
    }
    fclose(fp);
}

2. MallocTest2存在严重的栈溢出错误

int* arr[CAPACITY_DEBUG];是在栈上分配100万个指针,每个指针8字节,总大小约8MB,而Linux默认栈大小只有8MB左右,这直接导致栈溢出,覆盖了栈上的其他数据(比如函数返回地址、局部变量),这就是Valgrind报Invalid write/Invalid read错误的原因。

把栈上的指针数组改成堆分配:

// 替换原来的int* arr[CAPACITY_DEBUG];
int** arr = malloc(CAPACITY_DEBUG * sizeof(int*));
if (!arr) {
    perror("malloc arr");
    exit(1);
}

// 最后记得释放这个数组
free(arr);

3. free后RES不回落是正常的内存池机制,不是泄漏

glibc的malloc会把free的内存缓存起来,作为内存池供后续malloc复用,不会立刻把内存还给内核,所以top的RES不会立刻下降。这是为了减少系统调用开销、提升性能的优化行为,完全正常,不是内存泄漏。

如果一定要让内存还给内核,可以调用malloc_trim(0);(你代码里注释掉了这个函数),但频繁调用会影响性能,只建议在确实需要释放内存给系统时使用。

4. 正确跟踪内存泄漏的方法

  • 先确认是否真的泄漏:Valgrind的HEAP SUMMARY已经明确显示All heap blocks were freed -- no leaks are possible,说明测试程序没有内存泄漏,实际程序的崩溃可能是其他原因(比如栈溢出、内存越界)。
  • 用正确的工具跟踪内存变化:
    • 用/proc/self/status或/proc/self/statm获取当前RES值,而非ru_maxrss。
    • 用Valgrind的--tool=massif生成内存使用快照,分析内存增长的具体来源。
    • 用pmap -x <pid>查看进程的内存分布,确认哪些内存段在增长。
  • 排查实际程序的问题:如果实际程序RES持续增长,先通过Valgrind确认是否有未释放的内存,再检查是否是内存池缓存导致的“假增长”,最后排查是否存在栈溢出、内存越界破坏堆结构的情况。

内容的提问来源于stack exchange,提问作者Nafiul Alam Fuji

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 14:59:52