C语言学习笔记(十二):动态内存管理

1. 动态内存管理概述

1.1 为什么需要动态内存?

在C语言中,我们通常使用以下方式分配内存:

分配方式 特点 局限性

全局变量 程序整个运行期都存在 编译时确定大小,无法改变

局部变量 函数执行时存在 作用域受限,大小固定

静态变量 程序整个运行期都存在 编译时确定大小

动态内存的优势:

· ✅ 运行时按需分配

· ✅ 大小可以灵活调整

· ✅ 生命周期完全可控

· ✅ 适合处理不确定大小的数据

1.2 内存区域分布

高地址

+------------------+

| 栈区 | 局部变量、函数参数

| (Stack) | 自动分配释放,向下增长

+------------------+

| ↓ |

| |

| ↑ |

+------------------+

| 堆区 | 动态分配的内存

| (Heap) | malloc/free管理,向上增长

+------------------+

| BSS段 | 未初始化的全局/静态变量

+------------------+

| 数据段 | 初始化的全局/静态变量

| (Data) |

+------------------+

| 代码段 | 可执行代码、字符串常量

| (Text) |

+------------------+

低地址

2. 动态内存管理函数

C语言通过 <stdlib.h> 头文件提供了4个核心函数:

函数 功能 返回值

**malloc()**分配内存(不初始化) 成功返回指针,失败返回NULL

calloc() 分配并清零内存 成功返回指针,失败返回NULL

realloc() 重新调整内存大小 成功返回新指针,失败返回NULL

**free()**释放内存 void

2.1 malloc() - 内存分配

复制代码
#include <stdio.h>

#include <stdlib.h>


int main() {

    printf("===== malloc() 函数详解 =====\n\n");


    // 分配10个int大小的内存

    int *arr = (int*)malloc(10 * sizeof(int));
 
   // 检查分配是否成功

    if (arr == NULL) {

        printf("内存分配失败!\n");

        return 1;

    }

    printf("成功分配 %zu 字节内存\n", 10 * sizeof(int));

    printf("内存地址:%p\n", arr);

    // 注意:malloc 分配的内存内容是未初始化的(垃圾值)

    printf("未初始化的值:");

    for (int i = 0; i < 5; i++) {

        printf("%d ", arr[i]); // 可能输出随机值

    }

    printf("\n");
 
    // 手动初始化

    for (int i = 0; i < 10; i++) {

        arr[i] = i * 10;

    }  

    printf("初始化后的值:");

    for (int i = 0; i < 10; i++) {

        printf("%d ", arr[i]);

    }

    printf("\n"); 

    // 释放内存

    free(arr);

    printf("\n内存已释放\n");

    return 0;

}

2.2 calloc() - 连续分配并清零

复制代码
#include <stdio.h>

#include <stdlib.h>

int main() {

    printf("===== calloc() 函数详解 =====\n\n");

    // calloc(元素个数, 每个元素大小)

    // 分配10个int,并初始化为0

    int *arr = (int*)calloc(10, sizeof(int));

    if (arr == NULL) {

        printf("内存分配失败!\n");

        return 1;

    }

    printf("成功分配 %zu 字节内存\n", 10 * sizeof(int));

    printf("calloc 自动将所有字节初始化为0\n");

    printf("初始值:");

    for (int i = 0; i < 10; i++) {

        printf("%d ", arr[i]); // 全部为0

    }

    printf("\n");

    // malloc vs calloc 对比

    printf("\n===== malloc vs calloc =====\n\n");

    int *p1 = (int*)malloc(5 * sizeof(int));

    int *p2 = (int*)calloc(5, sizeof(int));

    printf("malloc 分配的内存:");

    for (int i = 0; i < 5; i++) {

        printf("%d ", p1[i]); // 随机值

    }

    printf("\n");

    printf("calloc 分配的内存:");

    for (int i = 0; i < 5; i++) {

        printf("%d ", p2[i]); // 全部为0

    } printf("\n");  

    free(p1);  free(p2);

    return 0;

}

2.3 realloc() - 重新调整内存大小

复制代码
#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main() {

    printf("===== realloc() 函数详解 =====\n\n");

    // 初始分配5个int

    int *arr = (int*)malloc(5 * sizeof(int));

    if (arr == NULL) {

        printf("初始分配失败\n");

        return 1;

    }

    // 初始化数据

    for (int i = 0; i < 5; i++) {

        arr[i] = i + 1;

    }

    printf("初始数组(5个元素):");

    for (int i = 0; i < 5; i++) {

        printf("%d ", arr[i]);

    }

    printf("\n");

    printf("原地址:%p\n", arr);

    // 扩展到10个int

    int *newArr = (int*)realloc(arr, 10 * sizeof(int));

    if (newArr == NULL) {

        printf("扩展失败!原内存未被释放\n");

        free(arr);

        return 1;

    }

    printf("\n扩展成功!\n");

    printf("新地址:%p\n", newArr);

    if newArr == arr) {

        printf("realloc 在原地址扩展\n");

    } else {

        printf("realloc 移动到了新地址,原数据已复制\n");

    }

    // 原有数据保持不变

    printf("原有数据:");

    for (int i = 0; i < 5; i++) {

        printf("%d ", newArr[i]);

    }

    printf("\n");

    // 新扩展的区域是未初始化的(垃圾值)

    printf("新扩展区域(未初始化):");

    for (int i = 5; i < 10; i++) {

        printf("%d ", newArr[i]);

    }  printf("\n");

    // 初始化新区域

    for (int i = 5; i < 10; i++) {

        newArr[i] = (i + 1) * 10;

    }printf("初始化后:");

    for (int i = 0; i < 10; i++) {

        printf("%d ", newArr[i]);

    }printf("\n");

    // 缩小内存(从10个缩小到3个)

    int *smallArr = (int*)realloc(newArr, 3 * sizeof(int));

    if (smallArr != NULL) {

        printf("\n缩小到3个元素:");

        for (int i = 0; i < 3; i++) {

            printf("%d ", smallArr[i]);

        }

        printf("\n");

        printf("注意:超出部分的数据已经丢失!\n");

        newArr = smallArr;

    }

    free(newArr);

    return 0;

}

2.4 free() - 释放内存

复制代码
#include <stdio.h>

#include <stdlib.h>



int main() {

    printf("===== free() 函数详解 =====\n\n");

    

    int *p = (int*)malloc(sizeof(int));

    if (p == NULL) {

        return 1;

    }

    

    *p = 100;

    printf("分配内存,地址:%p,值:%d\n", p, *p);

    

    // 释放内存

    free(p);

    printf("内存已释放\n");

    

    // ⚠️ 危险:释放后继续使用(悬空指针)

    // *p = 200; // 未定义行为!

    // printf("%d\n", *p); // 可能崩溃或输出错误值

    

    // 正确做法:释放后指针置为NULL

    p = NULL;

    

    // 可以安全地检查

    if (p == NULL) {

        printf("指针已置为NULL,无法使用\n");

    }

    

    // ⚠️ 注意:重复释放

    // free(p); // 对NULL指针free是安全的

    // free(p); // 但重复free非NULL指针会出错

    

    return 0;

}

3. 动态内存的典型应用

3.1 动态数组

复制代码
#include <stdio.h>

#include <stdlib.h>



// 动态数组结构体

typedef struct {

    int *data;

    size_t size;

    size_t capacity;

} DynamicArray;



// 初始化动态数组

DynamicArray* createArray(size_t initialCapacity) {

    DynamicArray *arr = (DynamicArray*)malloc(sizeof(DynamicArray));

    if (arr == NULL) return NULL;

    

    arr->data = (int*)malloc(initialCapacity * sizeof(int));

    if (arr->data == NULL) {

        free(arr);

        return NULL;

    }

    

    arr->size = 0;

    arr->capacity = initialCapacity;

    return arr;

}



// 向数组添加元素

int pushBack(DynamicArray *arr, int value) {

    if (arr->size >= arr->capacity) {

        // 扩容:容量翻倍

        size_t newCapacity = arr->capacity * 2;

        int *newData = (int*)realloc(arr->data, newCapacity * sizeof(int));

        if (newData == NULL) {

            return 0; // 扩容失败

        }

        arr->data = newData;

        arr->capacity = newCapacity;

        printf("扩容到 %zu\n", arr->capacity);

    }

    

    arr->data[arr->size] = value;

    arr->size++;

    return 1;

}



// 获取元素

int get(DynamicArray *arr, size_t index) {

    if (index >= arr->size) {

        return 0;

    }

    return arr->data[index];

}



// 释放数组

void destroyArray(DynamicArray *arr) {

    if (arr) {

        free(arr->data);

        free(arr);

    }

}



int main() {

    printf("===== 动态数组实现 =====\n\n");

    

    DynamicArray *arr = createArray(2); // 初始容量2

    if (arr == NULL) {

        printf("创建失败\n");

        return 1;

    }

    

    printf("添加10个元素:\n");

    for (int i = 1; i <= 10; i++) {

        pushBack(arr, i * 10);

        printf(" 添加 %d,当前大小:%zu,容量:%zu\n", 

               i * 10, arr->size, arr->capacity);

    }

    

    printf("\n数组内容:");

    for (size_t i = 0; i < arr->size; i++) {

        printf("%d ", get(arr, i));

    }

    printf("\n");

    

    destroyArray(arr);

    printf("\n数组已销毁\n");

    

    return 0;

}

3.2 动态字符串

复制代码
#include <stdio.h>

#include <stdlib.h>

#include <string.h>



// 动态字符串结构体

typedef struct {

    char *data;

    size_t length;

    size_t capacity;

} DynamicString;



// 创建动态字符串

DynamicString* createString(const char *initial) {

    DynamicString *str = (DynamicString*)malloc(sizeof(DynamicString));

    if (str == NULL) return NULL;

    

    size_t initLen = initial ? strlen(initial) : 0;

    str->capacity = initLen + 16; // 预留空间

    str->data = (char*)malloc((str->capacity + 1) * sizeof(char));

    

    if (str->data == NULL) {

        free(str);

        return NULL;

    }

    

    if (initial) {

        strcpy(str->data, initial);

        str->length = initLen;

    } else {

        str->data[0] = '\0';

        str->length = 0;

    }

    

    return str;

}



// 追加字符串

int appendString(DynamicString *str, const char *suffix) {

    size_t suffixLen = strlen(suffix);

    size_t newLen = str->length + suffixLen;

    

    // 检查是否需要扩容

    if (newLen > str->capacity) {

        size_t newCapacity = str->capacity * 2;

        while (newCapacity < newLen) {

            newCapacity *= 2;

        }

        

        char *newData = (char*)realloc(str->data, (newCapacity + 1) * sizeof(char));

        if (newData == NULL) {

            return 0;

        }

        str->data = newData;

        str->capacity = newCapacity;

    }

    

    // 追加字符串

    strcpy(str->data + str->length, suffix);

    str->length = newLen;

    

    return 1;

}



// 释放字符串

void destroyString(DynamicString *str) {

    if (str) {

        free(str->data);

        free(str);

    }

}



int main() {

    printf("===== 动态字符串实现 =====\n\n");

    

    DynamicString *str = createString("Hello");

    if (str == NULL) {

        printf("创建失败\n");

        return 1;

    }

    

    printf("初始字符串:\"%s\"\n", str->data);

    printf("长度:%zu,容量:%zu\n\n", str->length, str->capacity);

    

    // 追加字符串

    appendString(str, " ");

    appendString(str, "Dynamic");

    appendString(str, " ");

    appendString(str, "String");

    appendString(str, "!");

    

    printf("追加后:\"%s\"\n", str->data);

    printf("长度:%zu,容量:%zu\n", str->length, str->capacity);

    

    destroyString(str);

    

    return 0;

}

3.3 链表节点动态分配

复制代码
#include <stdio.h>

#include <stdlib.h>



// 链表节点

typedef struct Node {

    int data;

    struct Node *next;

} Node;



// 创建新节点

Node* createNode(int data) {

    Node *newNode = (Node*)malloc(sizeof(Node));

    if (newNode == NULL) {

        return NULL;

    }

    newNode->data = data;

    newNode->next = NULL;

    return newNode;

}



// 在头部插入

Node* insertAtHead(Node *head, int data) {

    Node *newNode = createNode(data);

    if (newNode == NULL) return head;

    

    newNode->next = head;

    return newNode;

}



// 在尾部插入

Node* insertAtTail(Node *head, int data) {

    Node *newNode = createNode(data);

    if (newNode == NULL) return head;

    

    if (head == NULL) {

        return newNode;

    }

    

    Node *current = head;

    while (current->next != NULL) {

        current = current->next;

    }

    current->next = newNode;

    

    return head;

}



// 删除节点

Node* deleteNode(Node *head, int data) {

    if (head == NULL) return NULL;

    

    // 删除头节点

    if (head->data == data) {

        Node *temp = head;

        head = head->next;

        free(temp);

        return head;

    }

    

    // 删除其他节点

    Node *current = head;

    while (current->next != NULL && current->next->data != data) {

        current = current->next;

    }

    

    if (current->next != NULL) {

        Node *temp = current->next;

        current->next = temp->next;

        free(temp);

    }

    

    return head;

}



// 打印链表

void printList(Node *head) {

    Node *current = head;

    while (current != NULL) {

        printf("%d -> ", current->data);

        current = current->next;

    }

    printf("NULL\n");

}



// 释放链表

void freeList(Node *head) {

    Node *current = head;

    while (current != NULL) {

        Node *temp = current;

        current = current->next;

        free(temp);

    }

}



int main() {

    printf("===== 动态链表实现 =====\n\n");

    

    Node *head = NULL;

    

    printf("插入元素:1, 2, 3, 4, 5\n");

    for (int i = 1; i <= 5; i++) {

        head = insertAtTail(head, i);

    }

    printList(head);

    

    printf("\n在头部插入0:\n");

    head = insertAtHead(head, 0);

    printList(head);

    

    printf("\n删除元素3:\n");

    head = deleteNode(head, 3);

    printList(head);

    

    printf("\n释放链表内存...\n");

    freeList(head);

    

    return 0;

}

4. 常见内存错误与调试

4.1 内存泄漏

复制代码
#include <stdio.h>

#include <stdlib.h>



// 内存泄漏示例

void memoryLeak() {

    int *p = (int*)malloc(sizeof(int));

    *p = 100;

    // 忘记调用 free(p) - 内存泄漏

    // 函数返回后,指针p被销毁,但分配的内存仍在堆上

}



// 正确的做法

void noLeak() {

    int *p = (int*)malloc(sizeof(int));

    if (p) {

        *p = 100;

        // 使用内存...

        free(p); // 必须释放

    }

}



// 循环中的内存泄漏

void loopLeak() {

    for (int i = 0; i < 100; i++) {

        int *p = (int*)malloc(sizeof(int));

        *p = i;

        // 使用后没有释放,每次循环都泄漏

    }

}



// 正确的循环分配

void loopNoLeak() {

    for (int i = 0; i < 100; i++) {

        int *p = (int*)malloc(sizeof(int));

        if (p) {

            *p = i;

            // 使用...

            free(p);

        }

    }

}



int main() {

    printf("===== 内存泄漏示例 =====\n\n");

    

    printf("内存泄漏的危害:\n");

    printf("- 程序占用内存不断增加\n");

    printf("- 长时间运行可能耗尽内存\n");

    printf("- 导致程序崩溃或系统变慢\n\n");

    

    printf("检测工具:\n");

    printf("- Valgrind: valgrind --leak-check=full ./program\n");

    printf("- AddressSanitizer: gcc -fsanitize=address program.c\n");

    

    return 0;

}

4.2 悬空指针

复制代码
#include <stdio.h>

#include <stdlib.h>



int* createNumber() {

    int num = 100; // 栈内存

    return &num; // ❌ 错误:返回局部变量的地址

}



int* createNumberHeap() {

    int *p = (int*)malloc(sizeof(int));

    if (p) {

        *p = 100;

    }

    return p; // ✅ 正确:返回堆内存地址

}



int main() {

    printf("===== 悬空指针示例 =====\n\n");

    

    // 错误1:返回局部变量地址

    int *p1 = createNumber();

    // printf("%d\n", *p1); // 未定义行为

    

    // 错误2:使用已释放的内存

    int *p2 = (int*)malloc(sizeof(int));

    *p2 = 200;

    free(p2);

    // *p2 = 300; // ❌ 错误:使用已释放的内存

    

    // 错误3:释放后未置NULL

    int *p3 = (int*)malloc(sizeof(int));

    free(p3);

    // if (p3 != NULL) { // p3不是NULL,但内存已释放

    // *p3 = 400; // 危险操作

    // }

    

    // 正确做法

    int *p4 = (int*)malloc(sizeof(int));

    if (p4) {

        *p4 = 500;

        free(p4);

        p4 = NULL; // 置为NULL,防止误用

    }

    

    printf("悬空指针预防措施:\n");

    printf("1. 释放后将指针置为NULL\n");

    printf("2. 不要返回局部变量的地址\n");

    printf("3. 使用前检查指针是否为NULL\n");

    

    return 0;

}

4.3 缓冲区溢出

复制代码
#include <stdio.h>

#include <stdlib.h>

#include <string.h>



int main() {

    printf("===== 缓冲区溢出示例 =====\n\n");

    

    // 错误1:数组越界

    int *arr = (int*)malloc(5 * sizeof(int));

    if (arr) {

        // arr[5] = 100; // ❌ 越界访问,数组索引0-4

        

        // 正确访问

        for (int i = 0; i < 5; i++) {

            arr[i] = i * 10;

        }

        free(arr);

    }

    

    // 错误2:字符串溢出

    char *str = (char*)malloc(10 * sizeof(char));

    if (str) {

        // strcpy(str, "This is a very long string"); // ❌ 缓冲区溢出

        

        // 正确做法:使用strncpy或确保空间足够

        strncpy(str, "Short", 9);

        str[9] = '\0';

        free(str);

    }

    

    // 错误3:忘记分配结束符空间

    char *name = (char*)malloc(5); // 只分配5字节

    // strcpy(name, "John"); // "John"需要5字节(包括'\0'),刚好够

    // strcpy(name, "Jonathan"); // ❌ 溢出

    

    printf("缓冲区溢出危害:\n");

    printf("- 覆盖相邻内存区域\n");

    printf("- 导致数据损坏\n");

    printf("- 可能被利用进行安全攻击\n");

    

    return 0;

}

5. 内存管理最佳实践

5.1 分配和释放配对原则

复制代码
#include <stdio.h>

#include <stdlib.h>



// 原则1:谁分配,谁释放

typedef struct {

    int *data;

    size_t size;

} MyArray;



// 创建函数负责分配

MyArray* createArray(size_t size) {

    MyArray *arr = (MyArray*)malloc(sizeof(MyArray));

    if (arr == NULL) return NULL;

    

    arr->data = (int*)malloc(size * sizeof(int));

    if (arr->data == NULL) {

        free(arr);

        return NULL;

    }

    

    arr->size = size;

    return arr;

}



// 销毁函数负责释放

void destroyArray(MyArray *arr) {

    if (arr) {

        free(arr->data);

        free(arr);

    }

}



// 原则2:使用后立即释放

void processData() {

    int *buffer = (int*)malloc(1024 * sizeof(int));

    if (buffer) {

        // 使用buffer...

        

        free(buffer); // 立即释放

        buffer = NULL;

    }

}



// 原则3:错误处理中释放资源

int riskyOperation() {

    int *p1 = (int*)malloc(sizeof(int));

    if (p1 == NULL) return -1;

    

    int *p2 = (int*)malloc(sizeof(int));

    if (p2 == NULL) {

        free(p1); // 错误时释放已分配的资源

        return -1;

    }

    

    // 正常处理...

    

    free(p1);

    free(p2);

    return 0;

}



int main() {

    printf("===== 内存管理最佳实践 =====\n\n");

    

    MyArray *arr = createArray(100);

    if (arr) {

        printf("数组创建成功\n");

        destroyArray(arr);

        printf("数组已销毁\n");

    }

    

    return 0;

}

5.2 内存分配包装器

复制代码
#include <stdio.h>

#include <stdlib.h>

#include <string.h>



// 带错误检查的malloc包装器

void* safeMalloc(size_t size) {

    void *ptr = malloc(size);

    if (ptr == NULL) {

        fprintf(stderr, "错误:内存分配失败,请求大小:%zu字节\n", size);

        exit(1);

    }

    return ptr;

}



// 带错误检查的calloc包装器

void* safeCalloc(size_t nmemb, size_t size) {

    void *ptr = calloc(nmemb, size);

    if (ptr == NULL) {

        fprintf(stderr, "错误:内存分配失败,请求元素数:%zu,每个大小:%zu\n", 

                nmemb, size);

        exit(1);

    }

    return ptr;

}



// 带错误检查的realloc包装器

void* safeRealloc(void *ptr, size_t size) {

    void *newPtr = realloc(ptr, size);

    if (newPtr == NULL && size != 0) {

        fprintf(stderr, "错误:内存重新分配失败,请求大小:%zu字节\n", size);

        free(ptr); // 原内存仍然有效

        exit(1);

    }

    return newPtr;

}



// 安全的内存复制

void* safeMemcpy(void *dest, const void *src, size_t n) {

    if (dest == NULL || src == NULL) {

        fprintf(stderr, "错误:memcpy参数为NULL\n");

        return NULL;

    }

    return memcpy(dest, src, n);

}



int main() {

    printf("===== 内存分配包装器 =====\n\n");

    

    // 使用安全分配函数

    int *arr = (int*)safeMalloc(100 * sizeof(int));

    

    for (int i = 0; i < 100; i++) {

        arr[i] = i;

    }

    

    // 安全扩容

    arr = (int*)safeRealloc(arr, 200 * sizeof(int));

    

    printf("内存分配成功,可以安全使用\n");

    

    free(arr);

    

    return 0;

}

6. 高级话题:内存池

复制代码
#include <stdio.h>

#include <stdlib.h>

#include <string.h>



#define POOL_SIZE 10240

#define ALIGNMENT 8



// 内存池结构

typedef struct MemoryPool {

    unsigned char *buffer;

    size_t size;

    size_t used;

    struct MemoryPool *next;

} MemoryPool;



// 内存池管理器

typedef struct {

    MemoryPool *pools;

    size_t poolSize;

} PoolManager;



// 对齐宏

#define ALIGN(value, alignment) \

    (((value) + (alignment) - 1) & ~((alignment) - 1))



// 创建内存池

MemoryPool* createPool(size_t size) {

    MemoryPool *pool = (MemoryPool*)malloc(sizeof(MemoryPool));

    if (pool == NULL) return NULL;

    

    pool->buffer = (unsigned char*)malloc(size);

    if (pool->buffer == NULL) {

        free(pool);

        return NULL;

    }

    

    pool->size = size;

    pool->used = 0;

    pool->next = NULL;

    

    return pool;

}



// 从内存池分配

void* poolAlloc(MemoryPool *pool, size_t size) {

    if (pool == NULL) return NULL;

    

    size_t alignedSize = ALIGN(size, ALIGNMENT);

    

    if (pool->used + alignedSize > pool->size) {

        return NULL; // 空间不足

    }

    

    void *ptr = pool->buffer + pool->used;

    pool->used += alignedSize;

    

    return ptr;

}



// 重置内存池

void poolReset(MemoryPool *pool) {

    if (pool) {

        pool->used = 0;

    }

}



// 销毁内存池

void destroyPool(MemoryPool *pool) {

    if (pool) {

        free(pool->buffer);

        free(pool);

    }

}



int main() {

    printf("===== 内存池实现 =====\n\n");

    

    // 创建内存池

    MemoryPool *pool = createPool(POOL_SIZE);

    if (pool == NULL) {

        printf("创建内存池失败\n");

        return 1;

    }

    

    printf("内存池创建成功,大小:%d 字节\n", POOL_SIZE);

    

    // 分配各种大小的内存

    int *a = (int*)poolAlloc(pool, sizeof(int));

    double *b = (double*)poolAlloc(pool, sizeof(double));

    char *str = (char*)poolAlloc(pool, 100);

    

    if (a && b && str) {

        *a = 42;

        *b = 3.14159;

        strcpy(str, "Memory Pool Example");

        

        printf("\n分配成功:\n");

        printf(" int: %d\n", *a);

        printf(" double: %.5f\n", *b);

        printf(" string: %s\n", str);

        printf(" 已使用:%zu 字节\n", pool->used);

        printf(" 剩余:%zu 字节\n", pool->size - pool->used);

    }

    

    // 重置内存池

    printf("\n重置内存池...\n");

    poolReset(pool);

    printf("重置后已使用:%zu 字节\n", pool->used);

    

    // 再次分配

    int *c = (int*)poolAlloc(pool, sizeof(int));

    *c = 100;

    printf("重置后新分配的值:%d\n", *c);

    

    // 销毁内存池

    destroyPool(pool);

    printf("内存池已销毁\n");

    

    return 0;

}

7. 调试工具与技巧

7.1 使用 Valgrind

复制代码
#include <stdio.h>

#include <stdlib.h>



// 编译:gcc -g program.c -o program

// 检测:valgrind --leak-check=full ./program



void valgrindTest() {

    // 内存泄漏示例

    int *leak = (int*)malloc(sizeof(int));

    *leak = 10;

    // 没有释放 - valgrind 会报告

    

    // 有效内存访问

    int *valid = (int*)malloc(sizeof(int));

    *valid = 20;

    free(valid);

    

    // 无效访问示例

    int *invalid = (int*)malloc(sizeof(int));

    // free(invalid);

    // *invalid = 30; // 使用已释放的内存

}



int main() {

    valgrindTest();

    return 0;

}

7.2 内存调试宏

复制代码
#include <stdio.h>

#include <stdlib.h>

#include <string.h>



// 调试宏

#define DEBUG_MEMORY 1



#if DEBUG_MEMORY

    #define MALLOC(size) debugMalloc(size, __FILE__, __LINE__)

    #define FREE(ptr) debugFree(ptr, __FILE__, __LINE__)

    

    typedef struct {

        void *ptr;

        size_t size;

        const char *file;

        int line;

    } MemRecord;

    

    #define MAX_RECORDS 1000

    static MemRecord records[MAX_RECORDS];

    static int recordCount = 0;

    

    void* debugMalloc(size_t size, const char *file, int line) {

        void *ptr = malloc(size);

        if (ptr && recordCount < MAX_RECORDS) {

            records[recordCount].ptr = ptr;

            records[recordCount].size = size;

            records[recordCount].file = file;

            records[recordCount].line = line;

            recordCount++;

            printf("[分配] %p (%zu字节) at %s:%d\n", ptr, size, file, line);

        }

        return ptr;

    }

    

    void debugFree(void *ptr, const char *file, int line) {

        for (int i = 0; i < recordCount; i++) {

            if (records[i].ptr == ptr) {

                printf("[释放] %p at %s:%d\n", ptr, file, line);

                // 移除记录

                records[i] = records[--recordCount];

                break;

            }

        }

        free(ptr);

    }

    

    void printLeaks() {

        if (recordCount > 0) {

            printf("\n===== 内存泄漏检测 =====\n");

            printf("发现 %d 处内存泄漏:\n", recordCount);

            for (int i = 0; i < recordCount; i++) {

                printf(" %p (%zu字节) allocated at %s:%d\n",

                       records[i].ptr, records[i].size,

                       records[i].file, records[i].line);

            }

        } else {

            printf("\n未发现内存泄漏\n");

        }

    }

#else

    #define MALLOC(size) malloc(size)

    #define FREE(ptr) free(ptr)

    #define printLeaks()

#endif



int main() {

    printf("===== 内存调试宏 =====\n\n");

    

    int *p1 = (int*)MALLOC(sizeof(int));

    int *p2 = (int*)MALLOC(10 * sizeof(int));

    char *p3 = (char*)MALLOC(100);

    

    // 使用内存...

    *p1 = 100;

    

    FREE(p1);

    // 故意不释放 p2 和 p3 来演示泄漏

    

    printLeaks();

    

    // 清理泄漏

    FREE(p2);

    FREE(p3);

    

    return 0;

}

8. 总结与最佳实践

8.1 核心要点

要点 说明

malloc 分配未初始化内存

calloc 分配并清零内存

realloc 调整内存大小

free 释放内存,必须配对使用

8.2 黄金法则

  1. 谁分配,谁释放

  2. 释放后立即置NULL

  3. 检查分配是否成功

  4. 避免重复释放

  5. 不要在栈上返回局部变量地址

  6. 使用工具检测内存问题

8.3 检查清单

// 动态内存使用检查清单

int checklist() {

int *ptr = NULL;

// ✅ 1. 分配前检查

ptr = (int*)malloc(sizeof(int));

if (ptr == NULL) {

return -1; // 处理错误

}

// ✅ 2. 使用内存

*ptr = 100;

// ✅ 3. 释放后置NULL

free(ptr);

ptr = NULL;

// ✅ 4. 避免重复释放

// free(ptr); // 对NULL释放是安全的

return 0;

}

8.4 常见陷阱速查

问题 表现 解决方案

内存泄漏 内存持续增长 确保每个malloc都有对应的free

悬空指针 程序崩溃或数据损坏 释放后置NULL

缓冲区溢出 数据损坏或安全漏洞 检查边界,使用安全函数

双重释放 程序崩溃 释放后置NULL

未初始化 随机值 使用calloc或手动初始化

记住:动态内存管理是C语言最强大也最危险的功能之一。正确使用可以让程序灵活

相关推荐
SuperEugene2 小时前
Vue3 + Element Plus 表格查询规范:条件管理、分页联动 + 避坑,标准化写法|表单与表格规范篇
开发语言·前端·javascript·vue.js·前端框架
小邓睡不饱耶2 小时前
东方财富网股票数据爬取实战:从接口分析到数据存储
开发语言·爬虫·python·网络爬虫
优化控制仿真模型2 小时前
【专八对答案!】2026年3月英语专业八级TEM8真题试卷及答案
经验分享
dapeng28702 小时前
C++与Docker集成开发
开发语言·c++·算法
2501_945423542 小时前
C++中的策略模式实战
开发语言·c++·算法
上海曼博生物医药科技有限公司2 小时前
Biolaminin在神经细胞培养中的应用:LN521与LN111如何支持功能性实现【曼博生物】
经验分享·业界资讯·细胞培养·biolamina·ln521·层粘连蛋白
2301_792308252 小时前
C++与自动驾驶系统
开发语言·c++·算法
hongtianzai2 小时前
Laravel8.x核心特性全解析
java·c语言·开发语言·golang·php
山上三树2 小时前
C/C++ 中,整数 ↔ 字符、整数 ↔ 字符串
c语言·c++