ToolkitX
知识库工具箱

内存管理

malloc, free, 内存泄漏, valgrind

25min·进阶

01. 栈和堆的区别

C 语言内存分四大区:栈、堆、全局区、代码区。栈是编译器自动管理的,函数调用时分配局部变量,返回时自动释放,非常快但空间有限。堆是程序员手动管理的,用 malloc 申请 free 释放,空间大但容易内存泄漏。理解栈和堆是 C 程序员的基本功。
c
#include <stdio.h>
#include <stdlib.h>

int global_var = 100; // 全局区

int main() {
    int stack_var = 42;     // 栈上分配
    
    // 堆上分配
    int *heap_var = (int*)malloc(sizeof(int));
    *heap_var = 99;
    
    printf("栈变量: %d\n", stack_var);
    printf("堆变量: %d\n", *heap_var);
    printf("全局变量: %d\n", global_var);
    
    free(heap_var); // 别忘了释放!
    return 0;
}
malloc 和 free 必须配对,忘 free 就内存泄漏,free 两次就 double free 崩溃。

02. malloc、calloc、realloc、free

malloc 分配指定字节数的内存,不初始化(里面是垃圾值)。calloc 分配并初始化为零,适合数组。realloc 调整已分配的内存大小(扩容或缩容),可能移动内存块地址。free 释放内存,指针 free 后最好置为 NULL 防止野指针。
c
#include <stdio.h>
#include <stdlib.h>

int main() {
    // malloc: 分配不初始化
    int *p1 = (int*)malloc(5 * sizeof(int));
    // 此时 p1[0] 的值不确定(垃圾值)
    
    // calloc: 分配并初始化为0
    int *p2 = (int*)calloc(5, sizeof(int));
    // p2[0] 到 p2[4] 都是 0
    
    // realloc: 扩容
    int *p3 = (int*)malloc(3 * sizeof(int));
    p3 = (int*)realloc(p3, 10 * sizeof(int));
    
    // 释放
    free(p1); free(p2); free(p3);
    p1 = p2 = p3 = NULL; // 好习惯
    
    return 0;
}
malloc 的参数可以算 sizeof:malloc(num * sizeof(int)),不管什么平台都不会出错。

03. 动态分配数组和结构体

用 malloc 分配数组比定长数组灵活,运行时才知道需要多大。动态分配结构体也很常见,比如链表的节点、树的节点。分配时别忘了 sizeof,二维数组可以用指针数组实现,每一行单独 malloc,行之间内存不连续但灵活。
c
#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node *next;
};

typedef struct Node Node;

Node* create_node(int data) {
    Node *node = (Node*)malloc(sizeof(Node));
    node->data = data;
    node->next = NULL;
    return node;
}

int main() {
    int n = 5;
    int *arr = (int*)malloc(n * sizeof(int));
    for (int i = 0; i < n; i++) arr[i] = i * 10;
    
    // 二维数组(指针数组)
    int rows = 3, cols = 4;
    int **matrix = (int**)malloc(rows * sizeof(int*));
    for (int i = 0; i < rows; i++) {
        matrix[i] = (int*)malloc(cols * sizeof(int));
    }
    
    // 使用...
    matrix[1][2] = 99;
    
    // 释放二维数组
    for (int i = 0; i < rows; i++) free(matrix[i]);
    free(matrix); free(arr);
    
    return 0;
}

04. 常见内存错误

C 语言最折磨人的就是内存错误,没有垃圾回收全靠自己管。常见五种:忘记 free(内存泄漏)、double free(重复释放崩溃)、野指针(用已经 free 了的指针)、越界访问(数组索引超出范围破坏其他数据)、缓冲区溢出(strcpy 拷贝超长字符串覆盖了返回地址)。valgrind 是查内存问题的神器。
c
#include <stdlib.h>
#include <string.h>

int main() {
    // 1. 内存泄漏 - 忘了 free
    int *p1 = malloc(100);
    // 没有 free(p1) - 泄漏!
    
    // 2. 野指针 - free 后还用
    int *p2 = malloc(100);
    free(p2);
    *p2 = 10; // 危险!野指针
    
    // 3. 缓冲区溢出
    char buf[10];
    strcpy(buf, "这个字符串太长了超过了10个字节"); // 溢出!
    
    // 4. 越界访问
    int arr[5];
    arr[10] = 100; // 越界到未知区域
    
    return 0;
}
内存泄漏在小程序里不明显,但服务端长期运行会慢慢耗尽内存导致崩溃。每次 malloc 都要想清楚谁负责 free。

05. 智能的内存管理实践

好的习惯能避免大部分内存问题:malloc 后立刻检查是否返回 NULL(分配失败);free 后把指针置 NULL;定义一个 xxx_free 函数集中释放资源;使用内存池减少碎片;避免深层嵌套指针,能栈分配的不用堆。配合 valgrind --leak-check=full 工具检查泄漏。
c
#include <stdio.h>
#include <stdlib.h>

struct User {
    char *name;
    int age;
};

typedef struct User User;

User* user_new(const char *name, int age) {
    User *u = malloc(sizeof(User));
    if (u == NULL) return NULL; // 检查分配失败
    u->name = malloc(strlen(name) + 1);
    if (u->name == NULL) { free(u); return NULL; }
    strcpy(u->name, name);
    u->age = age;
    return u;
}

void user_free(User *u) {
    if (u == NULL) return;
    free(u->name);
    free(u);
}

int main() {
    User *u = user_new("小明", 18);
    if (u == NULL) return 1;
    printf("%s, %d\n", u->name, u->age);
    user_free(u);
    // 检查: valgrind --leak-check=full ./program
    return 0;
}
每个数据结构写一对 new/free 函数,养成习惯后内存管理会清晰很多。

知识测验

1/4正确 0

malloc 和 calloc 的主要区别?

下一节

C++ 基础

下一节