【C语言】动态内存管理(malloc,free,calloc,realloc详解 )

🦄个人主页:小米里的大麦-CSDN博客

🎏所属专栏:https://blog.csdn.net/huangcancan666/category_12718530.html

🎁代码托管:C语言: C语言方向(基础知识和应用) (gitee.com)

⚙️操作环境:Visual Studio 2022

目录

一、引言

二、malloc

1. 简介

2. 语法

3. 使用方法

4. 关于内存的使用(重点)

工作原理:

示例一: malloc(sizeof(int))

示例二: malloc(20)

重要点:

5. 结论

三、free

1. 简介

2. 语法

3. 使用方法

4. 注意事项

 1. 只释放一次

2. 检查指针是否为 NULL

3. 避免悬挂指针

4. 动态数组的处理

5. 释放结构体中的指针

四、calloc

1. 简介

2. 语法

3. 使用方法

4. 注意事项

五、realloc

1. 简介

2. 语法

3. 使用方法

4. 注意事项

5. 缩容的语法和基本用法

缩容注意事项

realloc缩容的工作原理

缩容的实际应用场景

总结

六、总结

共勉


一、引言

动态内存管理是C语言中一个重要的部分,它允许程序在运行时动态地分配、使用和释放内存资源。这篇博客将详细讲解C语言中的四个核心函数:mallocfreecallocrealloc,并讨论它们的使用方法和注意事项。

二、malloc

1. 简介

malloc(memory allocation)用于动态分配指定大小的内存块。它从堆中分配内存,并返回指向该内存块的指针。分配的内存内容未初始化。(malloc - C++ Reference

2. 语法

#include <stdlib.h>//头文件
void* malloc(size_t size);//函数原型
  • size_t 是一个无符号整数类型,用来表示对象的大小。
  • size 参数指定要分配的内存字节数。
  • 如果成功分配内存,则 malloc 返回指向这块内存的指针;如果失败(例如没有足够的可用内存),则返回 NULL

3. 使用方法

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

int main() {
    int *p = (int*)malloc(sizeof(int) * 10); // 分配能存储10个int类型元素的内存

    if (p == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

    // 使用分配的内存
    for (int i = 0; i < 10; i++) {
        p[i] = i;
        printf("%d ", p[i]);
    }

    // 释放内存
    free(p);

    return 0;
}
分配内存:
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p;
    p = (int *)malloc(sizeof(int));  // 分配一个 int 类型大小的内存
    if (p == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    *p = 10;  // 给分配的内存赋值
    printf("值: %d\n", *p);  // 输出值
    free(p);  // 释放内存
    return 0;
}
分配多个元素:
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p;
    size_t num_elements = 5;
    p = (int *)malloc(num_elements * sizeof(int));
    if (p == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    for (int i = 0; i < num_elements; i++) {
        p[i] = i + 1;
    }
    for (int i = 0; i < num_elements; i++) {
        printf("%d ", p[i]);
    }
    printf("\n");
    free(p);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = (int*)malloc(sizeof(int)); // 分配4字节内存
    char *q = (char*)malloc(20);        // 分配20字节内存

    if (p == NULL || q == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

    *p = 100; // 存储一个int类型的值
    for (int i = 0; i < 20; i++) {
        q[i] = 'A'; // 使用分配的20字节空间
    }

    // 当你尝试访问 q[20](第21字节)时,程序行为未定义,可能会崩溃
    // q[20] = 'B'; // **不要这样做**,因为它会导致未定义行为

    printf("p: %d\n", *p);
    printf("q: %.20s\n", q);

    free(p); // 释放分配的内存
    free(q);

    return 0;
}

4. 关于内存的使用(重点)

  • 内存对齐:

实际上,malloc 不仅分配请求的字节数,还会考虑内存对齐的要求。这意味着它可能分配比你请求的更多的内存,以便确保内存地址能够适当地对齐。例如,如果系统要求int对齐到4字节边界,那么malloc在分配内存时,会确保返回的地址是4的倍数。如果当前的内存块不满足对齐要求,malloc可能会跳过一些字节(称为填充字节),以找到一个符合对齐要求的位置。

  • 内存碎片:

内存管理库会试图高效地管理内存,避免碎片化。意味着它可能会保留一些额外的内存,以备将来分配使用,但这不是用户直接控制的。

工作原理:

示例一: malloc(sizeof(int))
  • sizeof(int)通常为4(这取决于具体的系统和编译器),意味着malloc(sizeof(int))分配了4字节的内存。
  • malloc(sizeof(int))的返回值是一个指向这4字节内存块的指针。程序可以通过这个指针来存储和访问一个int类型的变量。
  • 总结malloc(sizeof(int))一次性分配了4字节的内存供程序使用。
示例二: malloc(20)
  • 当你调用malloc(20)时,内存管理器(通常由操作系统或运行时库管理)会在堆中找到一块足够大的、至少有20字节的空闲内存块,一次性分配给程序使用(将其分配给你)
  • 分配的20字节内存块起始地址由malloc返回,返回的指针指向这段20字节的内存块,你可以在这块内存上存储数据,但只能使用这20字节的空间。
  • 一旦这块内存分配给你,你就完全拥有了这20字节的空间,程序可以自由地在这个范围内操作。
  • 内存管理:如果你需要更多的空间,你需要手动调用realloc来调整这块内存的大小,或者分配一块新的、更大的内存块。

重要点

  • 一次性分配malloc(20)一次性从堆中分配20字节的内存。这20字节的内存是连续的,并且可以立即使用。
  • 使用限制:这20字节的内存并不会像“用一点给一点”那样逐渐分配,而是一次性分配。如果在使用过程中超过了这20字节的边界(例如试图访问第21字节),则可能会引发未定义行为,通常会导致程序崩溃或内存损坏。
  • 不会自动扩展malloc(20)分配的内存是固定的20字节。如果这20字节的空间用完了,程序必须手动使用realloc来扩展内存块,否则无法存储更多的数据。malloc本身不会自动再给20字节空间。

5. 结论

  • malloc是一次性分配:不管你请求多少字节,malloc都会一次性分配指定数量的内存给你,供你自由使用。
  • 内存不会自动扩展:一旦分配完内存,除非你使用realloc,否则内存块大小固定,不会自动增长。
  • 边界检查:在使用分配的内存时,必须确保不超出已分配的范围,否则会导致程序崩溃或数据损坏。
  • 内存对齐:malloc不仅分配你请求的内存量,还会考虑对齐要求,因此可能会分配比请求的更多的内存。其返回的内存地址会满足特定的对齐要求,确保高效且安全的内存访问。尽管你可能请求了例如malloc(20),实际分配的内存可能会大于20字节,以确保内存对齐,但对你而言,能安全使用的仍然是请求的20字节空间。

三、free

1. 简介

free用于释放由malloccallocrealloc分配的内存,使这部分内存重新可用。(手动释放内存)(free - C++ Reference

2. 语法

#include <stdlib.h>//头文件
void free(void *ptr);//函数原型
ptr: 是一个指向要释放的内存块的指针。如果 ptr 是 NULL,free 不做任何操作。

3. 使用方法

free的使用非常简单,以下是一个例子:

int *p = (int*)malloc(sizeof(int) * 10);
if (p != NULL) {
    // 使用内存
    free(p); // 释放内存
}

4. 注意事项

 1. 只释放一次

  • 不要对同一块内存多次调用 free。这会导致未定义行为,比如程序崩溃。
  • 不要对未分配的内存调用 free。确保你只释放通过 malloccalloc, 或 realloc 分配的内存。

2. 检查指针是否为 NULL

在调用 free 之前检查指针是否为 NULL:
if (p != NULL) {
    free(p);
    p = NULL; // 避免悬挂指针
}

3. 避免悬挂指针

当释放内存后,通常将指针设置为 NULL 来避免悬挂指针问题。
这样可以防止意外地再次尝试释放相同的内存。
free(p);
p = NULL;

4. 动态数组的处理

如果使用 malloc 或 calloc 分配了数组,释放内存时也需要正确处理:
int *arr = (int *)malloc(10 * sizeof(int));
...
free(arr);
arr = NULL;

5. 释放结构体中的指针

如果结构体中包含指向动态分配内存的指针,在销毁结构体前需要释放这些内存:
struct Node {
    int data;
    struct Node *next;
};

void freeNode(struct Node *node) {
    if (node != NULL) {
        freeNode(node->next); // 递归释放链表中的每个节点
        free(node);
    }
}

正确使用 free 函数是良好编程实践的一部分,它可以避免内存泄漏,并确保程序的稳定性和性能。

四、calloc

1. 简介

calloc(contiguous allocation)用于分配内存并初始化所有位为零。与malloc不同,它接受两个参数:分配的元素个数每个元素的大小。(calloc - C++ Reference

2. 语法

#include <stdlib.h>//头文件
void* calloc(size_t num, size_t size);//函数原型

参数说明

  • num:需要分配的元素数量。
  • size:每个元素的大小(以字节为单位)。

返回值

  • 如果内存分配成功,则返回指向新分配内存的指针。
  • 如果内存分配失败,则返回NULL

3. 使用方法

calloc()函数为所请求的元素数量分配一个连续的内存块,并且初始化所有元素为零值。意味着对于基本数据类型如int, float, 或者char等,所有元素都会被设置为0。

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

int main() {
    int *p = (int*)calloc(10, sizeof(int)); // 分配能存储10个int类型元素的内存,并初始化为0

    if (p == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

    // 使用分配的内存
    for (int i = 0; i < 10; i++) {
        printf("%d ", p[i]);
    }

    // 释放内存
    free(p);

    return 0;
}

4. 注意事项

  • malloc相似,calloc返回的指针也应检查是否为NULL
  • 使用完动态分配的内存后,必须调用free释放内存。

与 malloc 的区别

  • malloc()只分配内存但不初始化。
  • calloc()不仅分配内存,还会初始化所有元素为零值。
  • calloc()接受两个参数:元素个数和单个元素的大小;而malloc()仅接受一个参数:总大小。
如果你需要分配一段内存并且希望该内存被自动初始化为零,那么calloc()是一个更好的选择。
如果你只需要分配内存而不关心初始化,或者需要更灵活地控制内存大小,那么malloc()可能更适合。

这里有一个简单的例子来展示calloc()和malloc()的区别:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    int *array_calloc, *array_malloc;//array:数组
    int n = 5;

    // 使用 calloc
    array_calloc = (int *) calloc(n, sizeof(int));
    if (array_calloc == NULL) {
        printf("calloc failed\n");
        return 1;
    }
    for (int i = 0; i < n; i++) {
        printf("calloc[%d]: %d\n", i, array_calloc[i]);
    }
    free(array_calloc);

    // 使用 malloc
    array_malloc = (int *) malloc(n * sizeof(int));
    if (array_malloc == NULL) {
        printf("malloc failed\n");
        return 1;
    }
    memset(array_malloc, 0, n * sizeof(int));  // 手动初始化为零
    for (int i = 0; i < n; i++) {
        printf("malloc[%d]: %d\n", i, array_malloc[i]);
    }
    free(array_malloc);

    return 0;
}

五、realloc

1. 简介

realloc(reallocation)在 C 语言中用于改变已分配内存块的大小。这个函数允许您动态地增加或减少内存空间的大小,这对于需要根据运行时条件调整数据结构大小的应用程序非常有用。它可以扩展或缩小内存块,如果新大小大于旧大小,未初始化的新内存内容是不确定的。(realloc - C++ Reference

2. 语法

#include <stdlib.h>//头文件
void* realloc(void *ptr, size_t size);//函数原型
  • ptr: 指向要重新分配的内存块的指针(先前通过malloccallocrealloc分配的内存块的指针)。如果 ptr 是 NULL,那么 realloc() 将执行与 malloc(size) 相同的操作。
  • size: 新的内存大小(以字节为单位)。如果 size 为 0 并且 ptr 不是 NULL,那么 realloc() 将释放 ptr 指向的内存块,并返回一个 NULL 值。
  • 返回值:realloc() 返回一个指向新内存块的指针。如果内存重新分配成功,则返回的新指针可能与原来的指针不同。如果失败,它将返回 NULL。如果size为0,则相当于调用free(ptr),并返回NULL

3. 使用方法

以下是一个简单的例子:

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

int main() {
    int *p = (int*)malloc(sizeof(int) * 5); // 初始分配5个int类型元素的内存

    if (p == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

    // 扩展内存到10个int类型元素
    p = (int*)realloc(p, sizeof(int) * 10);

    if (p == NULL) {
        printf("内存重新分配失败\n");
        return 1;
    }

    // 使用扩展的内存
    for (int i = 0; i < 10; i++) {
        p[i] = i;
        printf("%d ", p[i]);
    }

    // 释放内存
    free(p);

    return 0;
}

4. 注意事项

  • realloc的返回值应检查是否为NULL,因为重新分配可能失败。
  • 如果realloc失败,原来的内存块仍然有效,应该避免内存泄漏
  • 如果新大小为0,realloc等同于调用free

5. 缩容的语法和基本用法

语法不变,同 realloc 的函数原型。

缩容注意事项

1. 数据保留:

  • 如果新的大小大于或等于原始大小,那么原始数据会被保留。
  • 如果新的大小小于原始大小,那么原始数据中超出新大小范围的部分将被丢弃。
  • 因此,在缩小内存之前,最好先备份重要数据,以防丢失。

2. 检查返回值:

  • 总是要检查 realloc() 的返回值是否为 NULL,这表示内存分配失败。
  • 如果返回值非 NULL,则需要将指针更新为新的地址。

3. 类型转换:

  • 类型转换通常需要应用到 realloc() 的返回值上,以保持类型安全。

4. 性能考虑:

  • 频繁缩容可能导致内存碎片化和性能降低。
  • 如果知道内存大小变化频繁,考虑使用其他数据结构或者技术,如自定义内存池。
以下是一个示例代码,展示了如何使用 realloc() 缩小内存大小:
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *array;
    int n = 10;
    int new_n;

    // 分配初始内存
    array = (int *) malloc(n * sizeof(int));

    if (array == NULL) {
        printf("Memory allocation failed!\n");
        return 1;
    }

    // 初始化数组
    for (int i = 0; i < n; i++) {
        array[i] = i + 1;
    }

    // 打印初始数组
    printf("Initial array:\n");
    for (int i = 0; i < n; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");

    // 缩小内存
    new_n = 5;
    array = (int *) realloc(array, new_n * sizeof(int));
    
    if (array == NULL) {
        printf("Memory reallocation failed!\n");
        free(array);
        return 1;
    }

    // 打印缩容后的数组
    printf("Reduced array:\n");
    for (int i = 0; i < new_n; i++) {
        printf("%d ", array[i]);
    }
    printf("\n");

    // 释放内存
    free(array);

    return 0;
}
在这个例子中,原始数组大小为 10,之后通过 realloc() 将其大小减小到了 5。
注意,缩容后只打印了前五个元素,这是因为原始数据中的后五个元素已经不再存在于新的内存块中。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    char *p = (char*)malloc(20 * sizeof(char)); // 分配20字节内存
    if (p == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

    strcpy(p, "Hello, World!"); // 向内存块中写入字符串

    // 缩小内存块到10字节
    p = (char*)realloc(p, 10 * sizeof(char));

    if (p == NULL) {
        printf("内存重新分配失败\n");
        return 1;
    }

    printf("缩容后的内容: %s\n", p); // 输出可能出现意外的内容

    free(p); // 释放内存

    return 0;
}
代码解析:

初始时,我们分配了20字节的内存并存储了字符串"Hello, World!"。
然后,我们使用realloc将内存块缩小到10字节。此时,只能保证前10字节的数据被保留。
由于字符串"Hello, World!"超过了10字节,后面的部分可能被裁剪或者造成字符串结尾缺失。

打印时,由于没有足够的空间保存完整的字符串以及字符串的终止符\0,因此可能会出现意外的输出(未定义行为)。

realloc缩容的工作原理

当你使用realloc缩减内存块的大小时(即size比原来分配的大小小),其行为如下:

  1. 数据保留realloc会保留内存块起始位置的一部分内容,保留的部分大小为size(new_size)。换句话说,缩容后的内存块中,前size字节的数据会保留并保持不变。

  2. 释放多余内存:对于超出size范围的多余内存,realloc会将其释放。具体的释放方式取决于内存管理器的实现,但对程序员来说,这部分内存块将不再可用,不能访问和使用。

  3. 指针返回:如果缩容后的内存块可以在原始地址上处理,那么realloc会直接返回原指针。如果内存管理器为了效率或其他原因在缩容过程中移动了内存块,realloc将返回一个新的内存地址,并且会将原来ptr指向的内容复制到新地址。

缩容的实际应用场景

缩容操作常用于以下场景:

  • 内存优化:在动态数据结构(如可变大小数组、缓冲区)中,随着数据量减少,使用realloc来释放多余的内存空间。
  • 节省资源:在程序运行中,及时缩减不再需要的内存块大小,可以节省系统资源,特别是在嵌入式系统或内存受限的环境中。

总结

通过合理使用realloc缩容,可以有效管理程序的内存资源,确保程序在执行过程中占用最小的内存空间,同时保持灵活性和高效性。

  • 在缩小内存时,一定要确保新大小能够容纳必要的数据。
  • 使用 realloc() 缩容时,必须检查返回值是否为 NULL
  • 对于经常需要改变大小的情况,考虑使用更高效的数据结构或内存管理策略。

六、总结

动态内存管理是C语言编程中不可或缺的一部分,正确使用mallocfreecallocrealloc可以提高程序的灵活性和效率。以下是一些关键点:

  • 始终检查内存分配函数的返回值是否为NULL
  • 使用完动态内存后,一定要释放内存,防止内存泄漏。
  • 对于复杂的数据结构,确保正确释放所有分配的内存,以避免内存泄漏和悬空指针。

掌握这些基本函数和相关注意事项,可以帮助你编写出更健壮和高效的C程序。希望这篇博客能帮助你更好地理解和使用C语言中的动态内存管理!

共勉

  • 15
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小米里的大麦

您的支持是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值