Skip to content

内存管理

C语言提供了手动管理内存的能力,这是C语言强大但也容易出错的地方。本章将详细介绍C语言的动态内存分配和管理。

内存区域

c
/*
 * C程序的内存布局
 */

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

/* 全局变量 - 存储在数据段 */
int global_init = 100;        /* 已初始化数据段 */
int global_uninit;            /* 未初始化数据段(BSS) */

/* 静态变量 */
static int static_var = 50;

/* 常量 - 存储在代码段/只读数据段 */
const int const_var = 10;

void memory_layout_demo(void)
{
    /* 局部变量 - 存储在栈上 */
    int local_var = 20;
    
    /* 动态分配 - 存储在堆上 */
    int *heap_var = (int*)malloc(sizeof(int));
    *heap_var = 30;
    
    printf("=== 内存区域地址 ===\n");
    printf("代码段(函数地址): %p\n", (void*)memory_layout_demo);
    printf("只读数据(常量): %p\n", (void*)&const_var);
    printf("已初始化数据段: %p\n", (void*)&global_init);
    printf("未初始化数据段: %p\n", (void*)&global_uninit);
    printf("栈(局部变量): %p\n", (void*)&local_var);
    printf("堆(动态分配): %p\n", (void*)heap_var);
    
    /*
     * 内存布局(从低地址到高地址):
     * 
     * +------------------+ 低地址
     * |    代码段        | 程序代码
     * +------------------+
     * |    只读数据段    | 字符串常量、const变量
     * +------------------+
     * |    数据段        | 已初始化的全局/静态变量
     * +------------------+
     * |    BSS段         | 未初始化的全局/静态变量
     * +------------------+
     * |    堆      ↑     | 动态分配,向上增长
     * |           ...    |
     * +------------------+
     * |    栈      ↓     | 局部变量,向下增长
     * +------------------+ 高地址
     * |    命令行参数    |
     * +------------------+
     */
    
    free(heap_var);
}

int main(void)
{
    memory_layout_demo();
    return 0;
}

malloc和free

c
/*
 * malloc 和 free 基本使用
 */

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

int main(void)
{
    /* malloc - 分配内存 */
    /* void* malloc(size_t size) */
    
    /* 分配单个整数 */
    int *p1 = (int*)malloc(sizeof(int));
    if (p1 == NULL) {
        printf("内存分配失败\n");
        return 1;
    }
    *p1 = 100;
    printf("p1指向的值: %d\n", *p1);
    free(p1);  /* 释放内存 */
    p1 = NULL; /* 避免野指针 */
    
    /* 分配数组 */
    int n = 5;
    int *arr = (int*)malloc(n * sizeof(int));
    if (arr == NULL) {
        printf("内存分配失败\n");
        return 1;
    }
    
    /* 初始化数组 */
    for (int i = 0; i < n; i++) {
        arr[i] = i * 10;
    }
    
    printf("\n动态数组: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    free(arr);
    arr = NULL;
    
    /* 常见错误示例 */
    printf("\n=== 常见错误 ===\n");
    
    /* 错误1:忘记free(内存泄漏) */
    int *leak = (int*)malloc(sizeof(int));
    *leak = 10;
    /* 忘记free(leak); */
    
    /* 错误2:重复free */
    int *double_free = (int*)malloc(sizeof(int));
    free(double_free);
    /* free(double_free); */  /* 错误!重复释放 */
    
    /* 错误3:free后继续使用 */
    int *use_after_free = (int*)malloc(sizeof(int));
    free(use_after_free);
    /* *use_after_free = 10; */  /* 错误!使用已释放的内存 */
    
    /* 错误4:free非动态分配的内存 */
    int stack_var = 10;
    /* free(&stack_var); */  /* 错误!不能释放栈上的内存 */
    
    printf("错误示例已注释,避免程序崩溃\n");
    
    return 0;
}

calloc和realloc

c
/*
 * calloc 和 realloc
 */

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

int main(void)
{
    /* calloc - 分配并初始化为0 */
    /* void* calloc(size_t num, size_t size) */
    
    int n = 5;
    int *arr1 = (int*)calloc(n, sizeof(int));
    
    if (arr1 != NULL) {
        printf("calloc分配的数组(自动初始化为0):\n");
        for (int i = 0; i < n; i++) {
            printf("arr1[%d] = %d\n", i, arr1[i]);
        }
        free(arr1);
    }
    
    /* malloc vs calloc */
    printf("\n=== malloc vs calloc ===\n");
    int *arr2 = (int*)malloc(n * sizeof(int));
    printf("malloc分配的数组(未初始化):\n");
    for (int i = 0; i < n; i++) {
        printf("arr2[%d] = %d\n", i, arr2[i]);  /* 可能是随机值 */
    }
    free(arr2);
    
    /* realloc - 重新分配内存大小 */
    /* void* realloc(void* ptr, size_t new_size) */
    
    printf("\n=== realloc 示例 ===\n");
    
    /* 初始分配 */
    int *arr3 = (int*)malloc(5 * sizeof(int));
    if (arr3 == NULL) {
        printf("分配失败\n");
        return 1;
    }
    
    /* 初始化 */
    for (int i = 0; i < 5; i++) {
        arr3[i] = i + 1;
    }
    
    printf("原始数组: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr3[i]);
    }
    printf("\n");
    
    /* 扩展数组 */
    int *arr3_new = (int*)realloc(arr3, 10 * sizeof(int));
    if (arr3_new == NULL) {
        printf("重新分配失败\n");
        free(arr3);
        return 1;
    }
    arr3 = arr3_new;  /* 使用新指针 */
    
    /* 初始化新增部分 */
    for (int i = 5; i < 10; i++) {
        arr3[i] = i + 1;
    }
    
    printf("扩展后数组: ");
    for (int i = 0; i < 10; i++) {
        printf("%d ", arr3[i]);
    }
    printf("\n");
    
    /* 缩小数组 */
    arr3_new = (int*)realloc(arr3, 3 * sizeof(int));
    if (arr3_new != NULL) {
        arr3 = arr3_new;
        
        printf("缩小后数组: ");
        for (int i = 0; i < 3; i++) {
            printf("%d ", arr3[i]);
        }
        printf("\n");
    }
    
    free(arr3);
    
    /* realloc的特殊用法 */
    printf("\n=== realloc特殊用法 ===\n");
    
    /* realloc(NULL, size) 等价于 malloc(size) */
    int *p = (int*)realloc(NULL, sizeof(int));
    *p = 100;
    printf("realloc(NULL, ...) = malloc: %d\n", *p);
    
    /* realloc(ptr, 0) 等价于 free(ptr) */
    realloc(p, 0);  /* 释放内存 */
    
    return 0;
}

动态数组实现

c
/*
 * 动态数组实现
 */

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

/* 动态数组结构体 */
typedef struct {
    int *data;      /* 数据指针 */
    size_t size;    /* 当前元素个数 */
    size_t capacity;/* 容量 */
} DynamicArray;

/* 初始化动态数组 */
int array_init(DynamicArray *arr, size_t initial_capacity)
{
    arr->data = (int*)malloc(initial_capacity * sizeof(int));
    if (arr->data == NULL) {
        return -1;  /* 失败 */
    }
    arr->size = 0;
    arr->capacity = initial_capacity;
    return 0;
}

/* 释放动态数组 */
void array_free(DynamicArray *arr)
{
    free(arr->data);
    arr->data = NULL;
    arr->size = 0;
    arr->capacity = 0;
}

/* 扩展容量 */
int array_expand(DynamicArray *arr)
{
    size_t new_capacity = arr->capacity * 2;
    int *new_data = (int*)realloc(arr->data, new_capacity * sizeof(int));
    if (new_data == NULL) {
        return -1;
    }
    arr->data = new_data;
    arr->capacity = new_capacity;
    printf("容量扩展到: %zu\n", new_capacity);
    return 0;
}

/* 添加元素 */
int array_push(DynamicArray *arr, int value)
{
    if (arr->size >= arr->capacity) {
        if (array_expand(arr) != 0) {
            return -1;
        }
    }
    arr->data[arr->size++] = value;
    return 0;
}

/* 获取元素 */
int array_get(DynamicArray *arr, size_t index)
{
    if (index >= arr->size) {
        printf("索引越界\n");
        return 0;
    }
    return arr->data[index];
}

/* 设置元素 */
int array_set(DynamicArray *arr, size_t index, int value)
{
    if (index >= arr->size) {
        printf("索引越界\n");
        return -1;
    }
    arr->data[index] = value;
    return 0;
}

/* 打印数组 */
void array_print(DynamicArray *arr)
{
    printf("数组内容(size=%zu, capacity=%zu): ", arr->size, arr->capacity);
    for (size_t i = 0; i < arr->size; i++) {
        printf("%d ", arr->data[i]);
    }
    printf("\n");
}

int main(void)
{
    DynamicArray arr;
    
    /* 初始化 */
    if (array_init(&arr, 4) != 0) {
        printf("初始化失败\n");
        return 1;
    }
    
    printf("初始状态:\n");
    array_print(&arr);
    
    /* 添加元素 */
    printf("\n添加元素:\n");
    for (int i = 1; i <= 10; i++) {
        array_push(&arr, i * 10);
        array_print(&arr);
    }
    
    /* 修改元素 */
    printf("\n修改元素:\n");
    array_set(&arr, 0, 100);
    array_print(&arr);
    
    /* 获取元素 */
    printf("\n获取元素:\n");
    printf("arr[5] = %d\n", array_get(&arr, 5));
    
    /* 释放 */
    array_free(&arr);
    
    return 0;
}

内存泄漏检测

c
/*
 * 内存泄漏检测技巧
 */

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

/* 简单的内存分配跟踪 */
#ifdef DEBUG_MEMORY

static size_t total_allocated = 0;
static size_t allocation_count = 0;

void* debug_malloc(size_t size, const char *file, int line)
{
    void *ptr = malloc(size);
    if (ptr != NULL) {
        total_allocated += size;
        allocation_count++;
        printf("[ALLOC] %p (%zu bytes) at %s:%d\n", 
               ptr, size, file, line);
    }
    return ptr;
}

void debug_free(void *ptr, const char *file, int line)
{
    if (ptr != NULL) {
        allocation_count--;
        printf("[FREE] %p at %s:%d\n", ptr, file, line);
    }
    free(ptr);
}

void memory_report(void)
{
    printf("\n=== 内存报告 ===\n");
    printf("总分配: %zu bytes\n", total_allocated);
    printf("未释放: %zu\n", allocation_count);
    if (allocation_count > 0) {
        printf("警告: 可能存在内存泄漏!\n");
    }
}

/* 替换标准函数 */
#define malloc(size) debug_malloc(size, __FILE__, __LINE__)
#define free(ptr) debug_free(ptr, __FILE__, __LINE__)

#endif

/* 使用Valgrind检测(Linux) */
/*
 * 编译: gcc -g program.c -o program
 * 运行: valgrind --leak-check=full ./program
 */

/* 使用AddressSanitizer(GCC/Clang) */
/*
 * 编译: gcc -fsanitize=address -g program.c -o program
 * 运行: ./program
 */

int main(void)
{
    printf("=== 内存管理最佳实践 ===\n\n");
    
    /* 1. 分配后检查返回值 */
    int *p1 = (int*)malloc(sizeof(int));
    if (p1 == NULL) {
        printf("分配失败\n");
        return 1;
    }
    
    /* 2. 使用后释放 */
    *p1 = 100;
    printf("值: %d\n", *p1);
    free(p1);
    
    /* 3. 释放后置NULL */
    p1 = NULL;
    
    /* 4. 谁分配谁释放 */
    int *create_array(int n) {
        int *arr = (int*)malloc(n * sizeof(int));
        return arr;  /* 调用者负责释放 */
    }
    
    void use_array(void) {
        int *arr = create_array(10);
        /* 使用数组 */
        free(arr);  /* 调用者释放 */
    }
    
    /* 5. 避免多次释放 */
    /* 6. 避免释放后使用 */
    /* 7. 注意realloc可能返回新地址 */
    
    printf("\n内存管理建议:\n");
    printf("1. 分配后检查返回值\n");
    printf("2. 使用后及时释放\n");
    printf("3. 释放后置NULL\n");
    printf("4. 使用工具检测泄漏\n");
    
    return 0;
}

小结

本章学习了:

  • 内存区域:代码段、数据段、BSS段、堆、栈
  • malloc/free:动态内存分配和释放
  • calloc:分配并初始化为0
  • realloc:重新分配内存大小
  • 动态数组:实现动态扩容的数组
  • 内存泄漏检测:调试技巧和工具

下一章,我们将学习预处理器,了解C语言的预处理指令。