ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

C语言:(14.动态内存补充)

C语言:(14.动态内存补充) 一、动态内存的底层原理1.1 堆区的内存管理堆区由内存分配器如ptmalloc、jemalloc管理它维护一个空闲链表。malloc的分配过程在空闲链表中查找足够大的块如果找到分割并返回如果找不到向操作系统申请更多内存free的释放过程将内存块标记为空闲尝试合并相邻的空闲块归还给操作系统可能不立即归还1.2 内存分配器的行为#include stdio.h #include stdlib.h int main() { // 第一次malloc会触发brk/sbrk系统调用 int* p1 (int*)malloc(100); printf(p1 %p\n, p1); // 第二次malloc可能从空闲链表中分配 int* p2 (int*)malloc(100); printf(p2 %p\n, p2); free(p1); free(p2); // 第三次malloc可能重用刚才释放的内存 int* p3 (int*)malloc(100); printf(p3 %p\n, p3); free(p3); return 0; }二、野指针与悬空指针2.1 野指针Wild Pointer野指针指向未知地址的指针。int* p; // 未初始化指向随机地址 *p 10; // 段错误常见的野指针场景// 场景1未初始化 int* p; printf(%p\n, p); // 随机地址 // 场景2指向已释放的内存 int* p (int*)malloc(40); free(p); *p 10; // p已经变成野指针 // 场景3返回局部变量的地址 int* func() { int a 10; return a; // a被销毁 }2.2 悬空指针Dangling Pointer悬空指针指向已释放内存的指针。int* p (int*)malloc(40); free(p); // p现在变成了悬空指针 // 解决方案释放后立即置NULL free(p); p NULL; // 不再是悬空指针三、内存泄漏的深度分析3.1 内存泄漏的常见场景// 场景1忘记释放 void func() { int* p (int*)malloc(100); // 使用p // 忘记free内存泄漏 } // 场景2指针被覆盖 int* p (int*)malloc(100); p (int*)malloc(200); // 第一次分配的100字节泄漏 free(p); // 场景3异常退出 int* p (int*)malloc(100); if (error_condition) { return; // 直接返回没有free } free(p); // 场景4循环中的内存泄漏 while (1) { int* p (int*)malloc(100); // 没有free每次循环泄漏100字节 }四、动态内存管理最佳实践4.1 黄金法则法则说明谁申请谁释放在同一个函数中malloc和free配对释放后置NULLfree(p); p NULL;检查返回值每次malloc/calloc/realloc都要检查避免多次释放使用NULL检查或标志位记录分配信息在大型项目中记录每个内存块的信息4.2 封装内存管理// 创建内存池 typedef struct { void* ptr; size_t size; int used; } MemBlock; typedef struct { MemBlock* blocks; int count; int capacity; } MemPool; MemPool* CreatePool(int capacity) { MemPool* pool (MemPool*)malloc(sizeof(MemPool)); if (pool NULL) return NULL; pool-blocks (MemBlock*)malloc(capacity * sizeof(MemBlock)); if (pool-blocks NULL) { free(pool); return NULL; } pool-count 0; pool-capacity capacity; return pool; } void* PoolAlloc(MemPool* pool, size_t size) { if (pool NULL || pool-count pool-capacity) { return NULL; } void* ptr malloc(size); if (ptr NULL) return NULL; pool-blocks[pool-count].ptr ptr; pool-blocks[pool-count].size size; pool-blocks[pool-count].used 1; pool-count; return ptr; } void PoolFree(MemPool* pool, void* ptr) { if (pool NULL || ptr NULL) return; for (int i 0; i pool-count; i) { if (pool-blocks[i].ptr ptr) { free(ptr); pool-blocks[i].used 0; pool-blocks[i].ptr NULL; return; } } } void DestroyPool(MemPool* pool) { if (pool NULL) return; // 释放所有未释放的内存 for (int i 0; i pool-count; i) { if (pool-blocks[i].used pool-blocks[i].ptr ! NULL) { free(pool-blocks[i].ptr); } } free(pool-blocks); free(pool); }五、综合实战案例案例1动态字符串#include stdio.h #include stdlib.h #include string.h typedef struct { char* data; size_t length; size_t capacity; } String; // 创建字符串 String* StringCreate(const char* str) { String* s (String*)malloc(sizeof(String)); if (s NULL) return NULL; s-length strlen(str); s-capacity s-length 1; s-data (char*)malloc(s-capacity); if (s-data NULL) { free(s); return NULL; } strcpy(s-data, str); return s; } // 追加字符串 int StringAppend(String* s, const char* str) { size_t new_len s-length strlen(str) 1; if (new_len s-capacity) { char* new_data (char*)realloc(s-data, new_len); if (new_data NULL) return -1; s-data new_data; s-capacity new_len; } strcat(s-data, str); s-length strlen(str); return 0; } // 释放字符串 void StringDestroy(String* s) { if (s NULL) return; free(s-data); free(s); } // 打印字符串 void StringPrint(const String* s) { if (s NULL || s-data NULL) return; printf(%s (len%zu, cap%zu)\n, s-data, s-length, s-capacity); } int main() { String* s StringCreate(Hello); if (s NULL) { printf(创建失败\n); return -1; } StringPrint(s); StringAppend(s, World); StringPrint(s); StringAppend(s, !); StringPrint(s); StringDestroy(s); return 0; }案例2动态二维数组#include stdio.h #include stdlib.h // 创建二维数组 int** Create2DArray(int rows, int cols) { // 分配行指针数组 int** arr (int**)malloc(rows * sizeof(int*)); if (arr NULL) return NULL; // 分配每一行 for (int i 0; i rows; i) { arr[i] (int*)malloc(cols * sizeof(int)); if (arr[i] NULL) { // 释放之前分配的行 for (int j 0; j i; j) { free(arr[j]); } free(arr); return NULL; } } return arr; } // 初始化二维数组 void Init2DArray(int** arr, int rows, int cols) { int value 1; for (int i 0; i rows; i) { for (int j 0; j cols; j) { arr[i][j] value; } } } // 打印二维数组 void Print2DArray(int** arr, int rows, int cols) { for (int i 0; i rows; i) { for (int j 0; j cols; j) { printf(%4d , arr[i][j]); } printf(\n); } } // 释放二维数组 void Free2DArray(int** arr, int rows) { if (arr NULL) return; for (int i 0; i rows; i) { free(arr[i]); } free(arr); } int main() { int rows 3, cols 4; int** arr Create2DArray(rows, cols); if (arr NULL) { printf(内存分配失败\n); return -1; } Init2DArray(arr, rows, cols); Print2DArray(arr, rows, cols); // 动态扩展添加一行 int** new_arr (int**)realloc(arr, (rows 1) * sizeof(int*)); if (new_arr NULL) { printf(扩容失败\n); Free2DArray(arr, rows); return -1; } arr new_arr; // 分配新行 arr[rows] (int*)malloc(cols * sizeof(int)); if (arr[rows] NULL) { Free2DArray(arr, rows); return -1; } // 初始化新行 for (int j 0; j cols; j) { arr[rows][j] 100 j; } rows; Print2DArray(arr, rows, cols); Free2DArray(arr, rows); return 0; }案例3链表实现动态内存经典应用#include stdio.h #include stdlib.h typedef struct Node { int data; struct Node* next; } Node; // 创建节点 Node* CreateNode(int data) { Node* node (Node*)malloc(sizeof(Node)); if (node NULL) return NULL; node-data data; node-next NULL; return node; } // 在头部插入 Node* InsertHead(Node* head, int data) { Node* node CreateNode(data); if (node NULL) return head; node-next head; return node; } // 在尾部插入 Node* InsertTail(Node* head, int data) { Node* node CreateNode(data); if (node NULL) return head; if (head NULL) return node; Node* cur head; while (cur-next ! NULL) { cur cur-next; } cur-next node; 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* cur head; while (cur-next ! NULL cur-next-data ! data) { cur cur-next; } if (cur-next ! NULL) { Node* temp cur-next; cur-next temp-next; free(temp); } return head; } // 打印链表 void PrintList(Node* head) { Node* cur head; while (cur ! NULL) { printf(%d - , cur-data); cur cur-next; } printf(NULL\n); } // 释放链表重要 void FreeList(Node* head) { Node* cur head; while (cur ! NULL) { Node* temp cur; cur cur-next; free(temp); } } int main() { Node* head NULL; head InsertTail(head, 1); head InsertTail(head, 2); head InsertTail(head, 3); head InsertHead(head, 0); PrintList(head); // 0 - 1 - 2 - 3 - NULL head DeleteNode(head, 2); PrintList(head); // 0 - 1 - 3 - NULL FreeList(head); // 重要释放所有节点 head NULL; return 0; }六、面试考点汇总考点1malloc(0)的返回值void* p malloc(0); // 行为未定义可能返回NULL或有效指针 // 标准规定size为0时行为由实现定义考点2free(NULL)free(NULL); // 安全什么都不做考点3realloc(NULL, size)void* p realloc(NULL, 100); // 等价于 malloc(100)考点4realloc(ptr, 0)void* p realloc(ptr, 0); // 等价于 free(ptr)返回NULL考点5常见笔试题void GetMemory(char* p) { p (char*)malloc(100); } int main() { char* str NULL; GetMemory(str); strcpy(str, hello); // str还是NULL程序崩溃 return 0; } 正确写法 void GetMemory(char** p) { *p (char*)malloc(100); } int main() { char* str NULL; GetMemory(str); if (str ! NULL) { strcpy(str, hello); printf(%s\n, str); free(str); } return 0; }七、高级总结知识点要点堆区管理空闲链表 brk/sbrk系统调用野指针未初始化或指向无效地址的指针悬空指针指向已释放内存的指针内存泄漏忘记释放 指针被覆盖 异常退出最佳实践谁申请谁释放 置NULL 检查返回值检测工具Valgrind、CRT调试、Dr. Memory八、课后作业实现一个动态数组Vector支持push_back、pop_back、insert、erase操作实现一个简单的内存池减少频繁malloc的开销分析以下代码的内存泄漏原因int* p (int*)malloc(10 * sizeof(int)); p (int*)realloc(p, 20 * sizeof(int)); // 如果realloc失败会发生什么用Valgrind检测一个程序的内存泄漏实现一个函数安全地复制字符串避免缓冲区溢出
返回列表