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 # // ❌ 错误:返回局部变量的地址
}
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 黄金法则
谁分配,谁释放
释放后立即置NULL
检查分配是否成功
避免重复释放
不要在栈上返回局部变量地址
使用工具检测内存问题
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语言最强大也最危险的功能之一。正确使用可以让程序灵活