|
|
@@ -0,0 +1,535 @@
|
|
|
+/**
|
|
|
+ * ============================================================================
|
|
|
+ * C 语言动态内存分配 · 学习示例
|
|
|
+ * ============================================================================
|
|
|
+ *
|
|
|
+ * 内容大纲:
|
|
|
+ * 第 1 节 malloc 基本内存分配
|
|
|
+ * 第 2 节 calloc 分配并初始化为 0
|
|
|
+ * 第 3 节 realloc 调整内存大小(扩容 / 缩容)
|
|
|
+ * 第 4 节 free 释放内存,防野指针
|
|
|
+ * 第 5 节 动态数组(自动扩容的 vector)
|
|
|
+ * 第 6 节 二维矩阵(指针数组 / 连续内存块 两种方式)
|
|
|
+ * 第 7 节 字符串数组(char** + strdup)
|
|
|
+ * 第 8 节 结构体数组(先数组、后成员的两层分配)
|
|
|
+ * 第 9 节 柔性数组成员(Flexible Array Member, C99)
|
|
|
+ * 第 10 节 常见错误、内存跟踪与良好习惯
|
|
|
+ *
|
|
|
+ * 编译运行(任选其一):
|
|
|
+ * gcc memory_allocation.c -o memory_allocation.exe -Wall -Wextra -g
|
|
|
+ * cl memory_allocation.c /Fe:memory_allocation.exe
|
|
|
+ *
|
|
|
+ * 说明:本程序按 C99 标准书写,注释使用中文,运行时请保证控制台
|
|
|
+ * 使用 UTF-8 编码(Windows 下可用 `chcp 65001`)。
|
|
|
+ * ============================================================================
|
|
|
+ */
|
|
|
+
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <string.h>
|
|
|
+
|
|
|
+/* ---------------------------------------------------------------------------
|
|
|
+ * 工具:带错误检查的分配包装
|
|
|
+ * ---------------------------------------------------------------------------
|
|
|
+ * malloc 失败时会返回 NULL。如果拿到 NULL 还继续使用,就是
|
|
|
+ * “空指针解引用”(NULL pointer dereference),程序直接崩溃。
|
|
|
+ * 下面的 xmalloc / xcalloc / xrealloc 把“分配 + 检查 + 报错退出”
|
|
|
+ * 封装起来,后面所有示例都用它们,保证代码既简洁又安全。
|
|
|
+ * ------------------------------------------------------------------------- */
|
|
|
+
|
|
|
+#define XMALLOC(size) xmalloc(size, __FILE__, __LINE__)
|
|
|
+#define XCALLOC(count, size) xcalloc(count, size, __FILE__, __LINE__)
|
|
|
+#define XREALLOC(ptr, size) xrealloc(ptr, size, __FILE__, __LINE__)
|
|
|
+
|
|
|
+static void *xmalloc(size_t size, const char *file, int line)
|
|
|
+{
|
|
|
+ void *p = malloc(size);
|
|
|
+ if (p == NULL) {
|
|
|
+ fprintf(stderr, "[内存分配失败] %s:%d 无法分配 %zu 字节\n",
|
|
|
+ file, line, size);
|
|
|
+ exit(EXIT_FAILURE);
|
|
|
+ }
|
|
|
+ return p;
|
|
|
+}
|
|
|
+
|
|
|
+static void *xcalloc(size_t count, size_t size, const char *file, int line)
|
|
|
+{
|
|
|
+ void *p = calloc(count, size);
|
|
|
+ if (p == NULL) {
|
|
|
+ fprintf(stderr, "[内存分配失败] %s:%d 无法分配 %zu x %zu 字节\n",
|
|
|
+ file, line, count, size);
|
|
|
+ exit(EXIT_FAILURE);
|
|
|
+ }
|
|
|
+ return p;
|
|
|
+}
|
|
|
+
|
|
|
+static void *xrealloc(void *ptr, size_t size, const char *file, int line)
|
|
|
+{
|
|
|
+ void *p = realloc(ptr, size); /* 注意:realloc 可能返回新地址 */
|
|
|
+ if (p == NULL) {
|
|
|
+ fprintf(stderr, "[内存重分配失败] %s:%d 无法调整到 %zu 字节\n",
|
|
|
+ file, line, size);
|
|
|
+ exit(EXIT_FAILURE);
|
|
|
+ }
|
|
|
+ return p;
|
|
|
+}
|
|
|
+
|
|
|
+/* 打印分隔标题,方便看清每个示例的输出 */
|
|
|
+static void section(const char *title)
|
|
|
+{
|
|
|
+ printf("\n================ %s ================\n", title);
|
|
|
+}
|
|
|
+
|
|
|
+/* ============================================================================
|
|
|
+ * 第 1 节 malloc —— 基本内存分配
|
|
|
+ * ----------------------------------------------------------------------------
|
|
|
+ * void *malloc(size_t size);
|
|
|
+ * - 在堆上分配 size 个字节的连续内存;
|
|
|
+ * - 返回 void*(无类型指针),使用时要强制转换成目标类型;
|
|
|
+ * - 分配的内存【内容不确定】,必须自己初始化后才能读取;
|
|
|
+ * - 分配失败返回 NULL。
|
|
|
+ * ==========================================================================*/
|
|
|
+static void demo_malloc(void)
|
|
|
+{
|
|
|
+ section("1. malloc 基本分配");
|
|
|
+
|
|
|
+ int *arr; /* 指向堆上 int 数组的指针 */
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /* 分配 5 个 int 需要的空间 */
|
|
|
+ arr = XMALLOC(5 * sizeof(int));
|
|
|
+ printf("成功分配 5 个 int(共 %zu 字节)\n", 5 * sizeof(int));
|
|
|
+
|
|
|
+ /*
|
|
|
+ * malloc 不会清零!真正未初始化时内容是无法预测的“垃圾值”。
|
|
|
+ * 直接读取未初始化的内存属于未定义行为,为了安全演示,
|
|
|
+ * 这里用 memset 把内存填成 0xAA,再把它当成 int 打印,
|
|
|
+ * 就能直观看到“这块内存里原来有未知的数据”。
|
|
|
+ */
|
|
|
+ memset(arr, 0xAA, 5 * sizeof(int));
|
|
|
+ printf("malloc 后未初始化(本处用 0xAA 模拟垃圾值):\n");
|
|
|
+ for (i = 0; i < 5; i++)
|
|
|
+ printf(" arr[%d] = 0x%08X\n", i, (unsigned)arr[i]);
|
|
|
+
|
|
|
+ /* 必须自己赋值后才能正常使用 */
|
|
|
+ for (i = 0; i < 5; i++)
|
|
|
+ arr[i] = i * i;
|
|
|
+ printf("手动初始化后:");
|
|
|
+ for (i = 0; i < 5; i++)
|
|
|
+ printf("%d ", arr[i]);
|
|
|
+ printf("\n");
|
|
|
+
|
|
|
+ free(arr);
|
|
|
+}
|
|
|
+
|
|
|
+/* ============================================================================
|
|
|
+ * 第 2 节 calloc —— 分配并清零
|
|
|
+ * ----------------------------------------------------------------------------
|
|
|
+ * void *calloc(size_t count, size_t size);
|
|
|
+ * - 分配 count * size 字节,并把所有字节置为 0;
|
|
|
+ * - 参数写成“个数 x 单个大小”,比 malloc(count*size) 更不容易写错、
|
|
|
+ * 还能避免整数溢出;
|
|
|
+ * - 同样要检查返回值是否为 NULL。
|
|
|
+ * ==========================================================================*/
|
|
|
+static void demo_calloc(void)
|
|
|
+{
|
|
|
+ section("2. calloc 分配并清零");
|
|
|
+
|
|
|
+ int *arr;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ arr = XCALLOC(5, sizeof(int));
|
|
|
+ printf("calloc 分配的 5 个 int 全部为 0:\n");
|
|
|
+ for (i = 0; i < 5; i++)
|
|
|
+ printf(" arr[%d] = %d\n", i, arr[i]);
|
|
|
+
|
|
|
+ free(arr);
|
|
|
+}
|
|
|
+
|
|
|
+/* ============================================================================
|
|
|
+ * 第 3 节 realloc —— 调整内存大小
|
|
|
+ * ----------------------------------------------------------------------------
|
|
|
+ * void *realloc(void *ptr, size_t new_size);
|
|
|
+ * - 把 ptr 指向的内存块调整到 new_size 字节;
|
|
|
+ * - 扩容时原有内容会被完整保留,新增部分【未初始化】;
|
|
|
+ * - 返回值可能是新的地址(原内存可能被搬走),所以一定要:
|
|
|
+ * ptr = realloc(ptr, new_size); // 重新赋给 ptr
|
|
|
+ * 绝不要写成:
|
|
|
+ * realloc(ptr, new_size); // 丢弃返回值 → 泄漏或悬垂
|
|
|
+ * - 传入 NULL 等价于 malloc;new_size 为 0 等价于 free(不推荐依赖)。
|
|
|
+ * ==========================================================================*/
|
|
|
+static void demo_realloc(void)
|
|
|
+{
|
|
|
+ section("3. realloc 扩容与缩容");
|
|
|
+
|
|
|
+ int *arr;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ arr = XMALLOC(4 * sizeof(int));
|
|
|
+ for (i = 0; i < 4; i++)
|
|
|
+ arr[i] = i + 1;
|
|
|
+ printf("初始 4 个元素:");
|
|
|
+ for (i = 0; i < 4; i++) printf("%d ", arr[i]);
|
|
|
+ printf("\n");
|
|
|
+
|
|
|
+ /* 扩容到 8 个 int:旧数据保留,新空间未初始化 */
|
|
|
+ arr = XREALLOC(arr, 8 * sizeof(int));
|
|
|
+ printf("扩容后前 4 个(保留的原数据):");
|
|
|
+ for (i = 0; i < 4; i++) printf("%d ", arr[i]);
|
|
|
+ printf("\n");
|
|
|
+
|
|
|
+ /* 新空间必须初始化后才能使用 */
|
|
|
+ for (i = 4; i < 8; i++)
|
|
|
+ arr[i] = i * 100;
|
|
|
+ printf("初始化后全部 8 个:");
|
|
|
+ for (i = 0; i < 8; i++) printf("%d ", arr[i]);
|
|
|
+ printf("\n");
|
|
|
+
|
|
|
+ /* 缩容到 3 个 int:多余的元素被丢弃 */
|
|
|
+ arr = XREALLOC(arr, 3 * sizeof(int));
|
|
|
+ printf("缩容后只剩 3 个:");
|
|
|
+ for (i = 0; i < 3; i++) printf("%d ", arr[i]);
|
|
|
+ printf("\n");
|
|
|
+
|
|
|
+ free(arr);
|
|
|
+}
|
|
|
+
|
|
|
+/* ============================================================================
|
|
|
+ * 第 4 节 free —— 释放内存,并防止野指针
|
|
|
+ * ----------------------------------------------------------------------------
|
|
|
+ * void free(void *ptr);
|
|
|
+ * - 只能释放 malloc / calloc / realloc 返回的指针;
|
|
|
+ * - 释放后该指针仍是原来的地址,但内存已归还系统,
|
|
|
+ * 这样的指针叫“野指针 / 悬垂指针”(dangling pointer),必须立刻置 NULL;
|
|
|
+ * - 对 NULL 调用 free 是安全的(什么都不做);
|
|
|
+ * - 同一块内存只能释放一次(重复释放 = double free,会崩溃)。
|
|
|
+ * ==========================================================================*/
|
|
|
+static void demo_free(void)
|
|
|
+{
|
|
|
+ section("4. free 与野指针");
|
|
|
+
|
|
|
+ int *p = XMALLOC(sizeof(int));
|
|
|
+ *p = 42;
|
|
|
+ printf("释放前 *p = %d\n", *p);
|
|
|
+
|
|
|
+ free(p);
|
|
|
+ /* 释放后 p 仍“指向”旧地址,继续读写它是未定义行为,必须置空 */
|
|
|
+ p = NULL;
|
|
|
+
|
|
|
+ if (p == NULL) {
|
|
|
+ printf("已释放并置空。对 NULL 调用 free 是安全的:\n");
|
|
|
+ free(p); /* 什么都不做,不会出错 */
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* ============================================================================
|
|
|
+ * 第 5 节 动态数组(可自动扩容的 int vector)
|
|
|
+ * ----------------------------------------------------------------------------
|
|
|
+ * 最常见的用法:用一个结构体记录“数据指针 + 元素个数 + 容量”,
|
|
|
+ * 满了就用 realloc 把容量翻倍。翻倍扩容后平均每个元素只被拷贝 O(1) 次,
|
|
|
+ * 所以 push 的均摊时间复杂度是 O(1)。
|
|
|
+ * ==========================================================================*/
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ int *data; /* 指向堆上数据 */
|
|
|
+ int len; /* 当前元素个数 */
|
|
|
+ int cap; /* 当前容量(已分配) */
|
|
|
+} IntVec;
|
|
|
+
|
|
|
+static void vec_init(IntVec *v)
|
|
|
+{
|
|
|
+ v->data = NULL;
|
|
|
+ v->len = 0;
|
|
|
+ v->cap = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void vec_push(IntVec *v, int value)
|
|
|
+{
|
|
|
+ if (v->len == v->cap) {
|
|
|
+ int new_cap = (v->cap == 0) ? 4 : v->cap * 2; /* 容量翻倍 */
|
|
|
+ v->data = XREALLOC(v->data, (size_t)new_cap * sizeof(int));
|
|
|
+ v->cap = new_cap;
|
|
|
+ }
|
|
|
+ v->data[v->len++] = value;
|
|
|
+}
|
|
|
+
|
|
|
+static void vec_free(IntVec *v)
|
|
|
+{
|
|
|
+ free(v->data);
|
|
|
+ v->data = NULL; /* 好习惯:释放后置空 */
|
|
|
+ v->len = 0;
|
|
|
+ v->cap = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void demo_vector(void)
|
|
|
+{
|
|
|
+ section("5. 动态数组 vector");
|
|
|
+
|
|
|
+ IntVec v;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ vec_init(&v);
|
|
|
+ for (i = 0; i < 10; i++)
|
|
|
+ vec_push(&v, i * 10);
|
|
|
+
|
|
|
+ printf("共 %d 个元素(容量 %d):\n", v.len, v.cap);
|
|
|
+ for (i = 0; i < v.len; i++)
|
|
|
+ printf(" v[%d] = %d\n", i, v.data[i]);
|
|
|
+
|
|
|
+ vec_free(&v);
|
|
|
+}
|
|
|
+
|
|
|
+/* ============================================================================
|
|
|
+ * 第 6 节 二维矩阵的两种分配方式
|
|
|
+ * ----------------------------------------------------------------------------
|
|
|
+ * 方式 A:指针数组 —— 每一行单独 malloc。写法直观、可以每行长度不同,
|
|
|
+ * 但各行之间地址不连续,对缓存不友好。
|
|
|
+ * 方式 B:连续内存块 —— 一次 malloc 出 rows*cols 个元素,再用一个
|
|
|
+ * 指针数组把大块内存“切”成一行一行。内存完全连续,性能更好,
|
|
|
+ * 释放也更简单。
|
|
|
+ * ==========================================================================*/
|
|
|
+static void demo_matrix(void)
|
|
|
+{
|
|
|
+ const int rows = 3, cols = 4;
|
|
|
+ int **m_a, **m_b, *storage;
|
|
|
+ int r, c;
|
|
|
+
|
|
|
+ /* ---- 方式 A:指针数组(每行单独分配) ---- */
|
|
|
+ m_a = XMALLOC((size_t)rows * sizeof(int *));
|
|
|
+ for (r = 0; r < rows; r++)
|
|
|
+ m_a[r] = XMALLOC((size_t)cols * sizeof(int));
|
|
|
+
|
|
|
+ for (r = 0; r < rows; r++)
|
|
|
+ for (c = 0; c < cols; c++)
|
|
|
+ m_a[r][c] = r * 10 + c;
|
|
|
+
|
|
|
+ printf("方式 A(每行单独 malloc):\n");
|
|
|
+ for (r = 0; r < rows; r++) {
|
|
|
+ printf(" ");
|
|
|
+ for (c = 0; c < cols; c++)
|
|
|
+ printf("%4d", m_a[r][c]);
|
|
|
+ printf("\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 释放顺序:先释放每一行,再释放行指针数组(顺序不能反) */
|
|
|
+ for (r = 0; r < rows; r++)
|
|
|
+ free(m_a[r]);
|
|
|
+ free(m_a);
|
|
|
+
|
|
|
+ /* ---- 方式 B:连续内存块 ---- */
|
|
|
+ storage = XMALLOC((size_t)(rows * cols) * sizeof(int));
|
|
|
+ m_b = XMALLOC((size_t)rows * sizeof(int *));
|
|
|
+ for (r = 0; r < rows; r++)
|
|
|
+ m_b[r] = storage + r * cols; /* 把大块内存划分成行 */
|
|
|
+
|
|
|
+ for (r = 0; r < rows; r++)
|
|
|
+ for (c = 0; c < cols; c++)
|
|
|
+ m_b[r][c] = r * 10 + c;
|
|
|
+
|
|
|
+ printf("方式 B(连续内存块):\n");
|
|
|
+ for (r = 0; r < rows; r++) {
|
|
|
+ printf(" ");
|
|
|
+ for (c = 0; c < cols; c++)
|
|
|
+ printf("%4d", m_b[r][c]);
|
|
|
+ printf("\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 方式 B 只需释放两次:先指针数组,再数据块 */
|
|
|
+ free(m_b);
|
|
|
+ free(storage);
|
|
|
+}
|
|
|
+
|
|
|
+/* ============================================================================
|
|
|
+ * 第 7 节 字符串数组
|
|
|
+ * ----------------------------------------------------------------------------
|
|
|
+ * 用 char** 存放多个字符串。字符串字面量不能随意修改,所以要把它们
|
|
|
+ * 复制到堆上(strdup:malloc + strcpy)。MSVC 里 strdup 叫 _strdup,
|
|
|
+ * 下面自己写一个 safe_strdup 保证跨平台可移植。
|
|
|
+ * ==========================================================================*/
|
|
|
+
|
|
|
+static char *safe_strdup(const char *s)
|
|
|
+{
|
|
|
+ size_t n = strlen(s) + 1; /* 别忘了末尾的 '\0' */
|
|
|
+ char *copy = XMALLOC(n);
|
|
|
+ memcpy(copy, s, n);
|
|
|
+ return copy;
|
|
|
+}
|
|
|
+
|
|
|
+static void demo_strings(void)
|
|
|
+{
|
|
|
+ section("7. 字符串数组");
|
|
|
+
|
|
|
+ const char *names[] = { "C", "Java", "Python", "Rust" };
|
|
|
+ int count = (int)(sizeof(names) / sizeof(names[0]));
|
|
|
+ char **strs = XMALLOC((size_t)count * sizeof(char *));
|
|
|
+ int i;
|
|
|
+
|
|
|
+ /* 每个字符串单独复制到堆上 */
|
|
|
+ for (i = 0; i < count; i++)
|
|
|
+ strs[i] = safe_strdup(names[i]);
|
|
|
+
|
|
|
+ printf("共 %d 个字符串:\n", count);
|
|
|
+ for (i = 0; i < count; i++)
|
|
|
+ printf(" strs[%d] = \"%s\"(长度 %zu)\n", i, strs[i], strlen(strs[i]));
|
|
|
+
|
|
|
+ /* 释放顺序:先每个字符串,再字符串指针数组 */
|
|
|
+ for (i = 0; i < count; i++)
|
|
|
+ free(strs[i]);
|
|
|
+ free(strs);
|
|
|
+}
|
|
|
+
|
|
|
+/* ============================================================================
|
|
|
+ * 第 8 节 结构体数组(两层分配)
|
|
|
+ * ----------------------------------------------------------------------------
|
|
|
+ * 结构体里含指针时,内存要分两层分配:
|
|
|
+ * 1. 给“结构体数组”分配内存;
|
|
|
+ * 2. 给每个结构体内部的指针成员分配内存。
|
|
|
+ * 释放顺序与分配顺序相反:先释放每个成员的指针,再释放数组本身。
|
|
|
+ * ==========================================================================*/
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ char *name; /* 指向堆上的字符串 */
|
|
|
+ int score;
|
|
|
+} Student;
|
|
|
+
|
|
|
+static void demo_struct_array(void)
|
|
|
+{
|
|
|
+ section("8. 结构体数组");
|
|
|
+
|
|
|
+ const char *stu_names[] = { "Alice", "Bob", "Carol" };
|
|
|
+ int count = (int)(sizeof(stu_names) / sizeof(stu_names[0]));
|
|
|
+ Student *list = XMALLOC((size_t)count * sizeof(Student));
|
|
|
+ int i;
|
|
|
+
|
|
|
+ for (i = 0; i < count; i++) {
|
|
|
+ list[i].name = safe_strdup(stu_names[i]);
|
|
|
+ list[i].score = 60 + i * 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ printf("学生列表:\n");
|
|
|
+ for (i = 0; i < count; i++)
|
|
|
+ printf(" %-6s 得分 %d\n", list[i].name, list[i].score);
|
|
|
+
|
|
|
+ /* 释放:先释放每个成员指针,再释放结构体数组 */
|
|
|
+ for (i = 0; i < count; i++)
|
|
|
+ free(list[i].name);
|
|
|
+ free(list);
|
|
|
+}
|
|
|
+
|
|
|
+/* ============================================================================
|
|
|
+ * 第 9 节 柔性数组成员(Flexible Array Member, C99)
|
|
|
+ * ----------------------------------------------------------------------------
|
|
|
+ * 结构体的最后一个成员可以写成不指定长度的数组 items[],
|
|
|
+ * 它不占结构体本身的空间。这样“结构体头 + 数据”可以用一次 malloc
|
|
|
+ * 分配在同一块连续内存里,访问时 items 紧跟在头后面,释放也只需 free 一次。
|
|
|
+ * ==========================================================================*/
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ size_t len; /* 元素个数 */
|
|
|
+ int items[]; /* 柔性数组成员,sizeof(Pack) 不含它 */
|
|
|
+} Pack;
|
|
|
+
|
|
|
+static void demo_flexible_array(void)
|
|
|
+{
|
|
|
+ section("9. 柔性数组成员");
|
|
|
+
|
|
|
+ size_t n = 6;
|
|
|
+ Pack *pack = XMALLOC(sizeof(Pack) + n * sizeof(int));
|
|
|
+ size_t i;
|
|
|
+
|
|
|
+ pack->len = n;
|
|
|
+ for (i = 0; i < n; i++)
|
|
|
+ pack->items[i] = (int)i * 2;
|
|
|
+
|
|
|
+ printf("sizeof(Pack) = %zu(只包含 len,不含 items)\n", sizeof(Pack));
|
|
|
+ printf("共 %zu 个元素:", pack->len);
|
|
|
+ for (i = 0; i < pack->len; i++)
|
|
|
+ printf("%d ", pack->items[i]);
|
|
|
+ printf("\n");
|
|
|
+ printf("一次 malloc、一次 free:数据与结构体头在同一个内存块里。\n");
|
|
|
+
|
|
|
+ free(pack);
|
|
|
+}
|
|
|
+
|
|
|
+/* ============================================================================
|
|
|
+ * 第 10 节 常见错误、内存跟踪与良好习惯
|
|
|
+ * ----------------------------------------------------------------------------
|
|
|
+ * 常见错误速查:
|
|
|
+ * 1. 不检查 malloc 的返回值 → 见 xmalloc 包装
|
|
|
+ * 2. 分配了却忘记 free → 内存泄漏(memory leak)
|
|
|
+ * 3. 越界写(分配 5 个却写第 6 个) → 堆损坏,难排查
|
|
|
+ * 4. free 之后继续使用(野指针) → 见第 4 节
|
|
|
+ * 5. 同一指针重复 free(double free)→ 崩溃
|
|
|
+ * 6. 释放与分配方式不匹配 → C 中统一用 free
|
|
|
+ *
|
|
|
+ * 下面的跟踪计数器用于演示“谁分配、谁释放”,借此直观看出泄漏:
|
|
|
+ * 分配次数 - 释放次数 > 0 就说明有内存没还回去。
|
|
|
+ * ==========================================================================*/
|
|
|
+
|
|
|
+static int g_alloc_count = 0;
|
|
|
+static int g_free_count = 0;
|
|
|
+
|
|
|
+#define TRACK_MALLOC(size) track_malloc(size, __FILE__, __LINE__)
|
|
|
+#define TRACK_FREE(ptr) do { track_free(ptr, __FILE__, __LINE__); ptr = NULL; } while (0)
|
|
|
+
|
|
|
+static void *track_malloc(size_t size, const char *file, int line)
|
|
|
+{
|
|
|
+ void *p = xmalloc(size, file, line);
|
|
|
+ g_alloc_count++;
|
|
|
+ printf(" [跟踪] 分配 0x%p(%zu 字节)@ %s:%d\n", p, size, file, line);
|
|
|
+ return p;
|
|
|
+}
|
|
|
+
|
|
|
+static void track_free(void *p, const char *file, int line)
|
|
|
+{
|
|
|
+ if (p != NULL) {
|
|
|
+ g_free_count++;
|
|
|
+ printf(" [跟踪] 释放 0x%p @ %s:%d\n", p, file, line);
|
|
|
+ }
|
|
|
+ free(p);
|
|
|
+}
|
|
|
+
|
|
|
+static void demo_pitfalls(void)
|
|
|
+{
|
|
|
+ section("10. 内存跟踪:谁分配、谁释放");
|
|
|
+
|
|
|
+ /* 故意“分配后不释放”,制造一块泄漏内存(学习观察用) */
|
|
|
+ int *leak = TRACK_MALLOC(100 * sizeof(int));
|
|
|
+ (void)leak; /* 避免“未使用变量”警告 */
|
|
|
+
|
|
|
+ /* 正确的一对:分配后释放 */
|
|
|
+ int *ok = TRACK_MALLOC(10 * sizeof(int));
|
|
|
+ TRACK_FREE(ok);
|
|
|
+
|
|
|
+ printf("\n 已分配 %d 次,已释放 %d 次,差值 %d(>0 表示有泄漏)\n",
|
|
|
+ g_alloc_count, g_free_count, g_alloc_count - g_free_count);
|
|
|
+ if (g_alloc_count != g_free_count)
|
|
|
+ printf(" >> 发现 %d 块内存未释放(人为制造的泄漏,仅供学习)<<\n",
|
|
|
+ g_alloc_count - g_free_count);
|
|
|
+
|
|
|
+ printf("\n 提示:真实项目中可用 Valgrind(Linux)或 AddressSanitizer\n"
|
|
|
+ " (gcc -fsanitize=address)自动检测泄漏与越界。\n");
|
|
|
+}
|
|
|
+
|
|
|
+/* ============================================================================
|
|
|
+ * 程序入口:依次运行所有示例
|
|
|
+ * ==========================================================================*/
|
|
|
+int main(void)
|
|
|
+{
|
|
|
+ /* 关闭 stdout 缓冲,保证 Windows 控制台也能即时显示中文输出 */
|
|
|
+ setvbuf(stdout, NULL, _IONBF, 0);
|
|
|
+
|
|
|
+ demo_malloc();
|
|
|
+ demo_calloc();
|
|
|
+ demo_realloc();
|
|
|
+ demo_free();
|
|
|
+ demo_vector();
|
|
|
+ demo_matrix();
|
|
|
+ demo_strings();
|
|
|
+ demo_struct_array();
|
|
|
+ demo_flexible_array();
|
|
|
+ demo_pitfalls();
|
|
|
+
|
|
|
+ printf("\n全部示例运行完毕!\n");
|
|
|
+ return 0;
|
|
|
+}
|