C语言动态内存分配:malloc、free、calloc与realloc详解

向北走
向北走
发布时间:2026-10-15 | 5人浏览过
原创

C语言程序运行时会用到不同的存储区域,其中全局变量和静态变量存放在静态区,局部变量和形参存放在栈区。而当我们需要在运行时动态决定内存大小时,就需要向堆区申请空间,这主要通过<stdlib.h>头文件中的四个核心函数——malloc、free、calloc和realloc来实现。

一、 malloc:向堆区申请内存

void* malloc(size_t size) 是动态内存分配最常用的函数。它的作用是在堆上分配一块连续的、可用的字节空间,并返回指向该空间起始位置的指针。

  • 未初始化:新分配的内存块内容是未初始化的,其中的数据值不确定。
  • 零大小处理:如果传入的 size 为 0,返回值取决于具体的库实现(可能是 NULL,也可能不是)。
  • 返回值类型:返回值为 void* 类型。在 C++ 中由于严格的类型检查必须进行强制类型转换,而在 C 语言中虽然可以隐式转换,但为了安全和规范,通常也会显式转换为目标类型的指针。
  • 失败处理:如果无法分配请求的空间,函数将返回空指针 NULL。

第1步:正确调用 malloc 并判断返回值

在分配内存后,必须首先检查指针是否为 NULL,防止因内存不足导致的非法操作。以下代码展示了如何申请一个包含 10 个整数的空间,并使用完毕后安全释放:

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
int main()
{
    int* p = (int*)malloc(10 * sizeof(int));
    if (p == NULL)
    {
        printf("%s\n", strerror(errno));
    }
    else
    {
        for (int i = 0; i < 10; ++i)
        {
            *(p + i) = i;
        }
        for (int i = 0; i < 10; ++i)
        {
            printf("%d ", *(p + i));
        }
    }
    free(p);
    p = NULL;
    return 0;
}

执行上述代码后,控制台输出结果如下:

0 1 2 3 4 5 6 7 8 9

二、 free:释放已分配的内存

void free(void* ptr) 用于归还由 malloc、calloc 或 realloc 分配的内存块,使其可以被操作系统重新回收。

  • 行为定义:如果 ptr 指向的不是由动态内存分配函数申请的内存,其行为是未定义的(Undefined Behavior)。
  • 空指针处理:如果 ptr 是 NULL,free 函数不会执行任何操作,这是安全的。
  • 切断联系:free 仅释放堆区空间的管理权,并不会修改指针变量本身的值。因此,释放后指针仍然指向原来的地址(悬垂指针)。为防止对同一段空间进行二次释放(导致崩溃),务必在调用 free 后将指针置为 NULL。

第2步:避免传递错误的指针

如果在释放前移动了原始指针的位置,例如先遍历输出再释放,会导致程序崩溃,因为释放的是非初始位置的指针:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int* p = (int*)malloc(sizeof(int) * 10);
    if (p == NULL)
    {
        return 1;
    }
    for (int i = 0; i < 10; ++i)
    {
        *(p + i) = i;
    }
    // 这里指针发生了移动
    for (int i = 0; i < 10; ++i)
    {
        printf("%d ", *(p++));
    }
    free(p); // 此时 p 已经指向了原分配空间的末尾之外,属于非法释放,程序会崩溃
    p = NULL;
    return 0;
}

三、 calloc:带初始化的内存分配

void* calloc(size_t num, size_t size) 同样用于分配堆内存,但它的功能更明确:分配 num 个大小为 size 的元素构成的数组,并自动将每一块字节的值都初始化为 0。

它与 malloc 唯一的区别就在于这一步“清零”操作。在处理结构体或需要预知数据的数组时,使用 calloc 可以避免手动循环赋值,更加简洁安全。

第3步:利用 calloc 输入和处理数据

以下代码演示了如何通过用户输入动态确定元素个数,并利用 calloc 的特性进行数据存储:

#include <stdio.h>      /* printf, scanf, NULL */
#include <stdlib.h>     /* calloc, exit, free */
int main ()
{
  int i,n;
  int * pData;
  printf ("Amount of numbers to be entered: ");
  scanf ("%d",&i);
  pData = (int*) calloc (i,sizeof(int));
  if (pData==NULL) exit (1);
  for (n=0;n<i;n++)
  {
    printf ("Enter number #%d: ",n+1);
    scanf ("%d",&pData[n]);
  }
  printf ("You have entered: ");
  for (n=0;n<i;n++) printf ("%d ",pData[n]);
  free (pData);
  return 0;
}

四、 realloc:调整内存块大小

当我们在运行过程中发现之前申请的内存不够用了,或者想减少内存占用时,可以使用 realloc。

void* realloc(void* ptr, size_t size) 会根据新的 size 需求,调整指针 ptr 所指向的内存块。

  • 就地扩展:如果 ptr 指向的当前内存后面还有足够的连续空间,它会直接在原位置追加内存,并返回原指针地址。
  • 异地搬迁:如果后面的空间不够,系统会在堆区的其他地方找一块足够大的新空间,把旧数据拷贝过去,释放掉旧空间,然后返回这块新内存块的起始地址。

注意:由于存在异地搬迁的情况,realloc 可能返回一个新的地址。我们应当用一个临时指针来接收返回值,确认成功后再更新原指针,以防原指针丢失导致内存泄漏。

第4步:安全地使用 realloc 扩容

以下代码实现了不断读取整数直到输入 0 为止的功能,并在每次添加新数字时动态扩容:

#include <stdio.h>      /* printf, scanf, puts */
#include <stdlib.h>     /* realloc, free, exit, NULL */
int main ()
{
  int input,n;
  int count = 0;
  int* numbers = NULL;
  int* more_numbers = NULL;
  do {
     printf ("Enter an integer value (0 to end): ");
     scanf ("%d", &input);
     count++;

     // 使用临时指针接收 realloc 的结果
     more_numbers = (int*) realloc (numbers, count * sizeof(int));

     if (more_numbers!=NULL) {
       numbers=more_numbers; // 只有成功后才更新主指针
       numbers[count-1]=input;
     }
     else {
       free (numbers);       // 扩容失败时,仍需释放原有内存
       puts ("Error (re)allocating memory");
       exit (1);
     }
  } while (input!=0);
  printf ("Numbers entered: ");
  for (n=0;n<count;n++) printf ("%d ",numbers[n]);
  free (numbers);
  return 0;
}

五、常见的动态内存分配错误

在使用动态内存管理时,请务必留意以下六大陷阱:

  1. 对 NULL 指针的解引用:没有检查 malloc 等的返回值就直接使用指针。
  2. 越界访问:超出了实际分配的内存范围进行读写。
  3. 误用 free:尝试 free 那些不是动态开辟的内存(如栈区变量或静态区变量)。
  4. 释放部分内存:试图只释放动态空间的一部分。如果需要释放特定位置之后的内容,必须先归位指针,再进行释放。
  5. 多次释放:对同一块动态内存在未赋新值的情况下调用了两次 free。
  6. 忘记释放:程序退出前未调用 free,导致进程生命周期内内存持续增长,即内存泄漏。

六、柔性数组:优化内存结构

C99 标准引入了一个特殊的概念:允许结构体的最后一个成员是一个未知大小的数组,被称为柔性数组成员(Flexible Array Member)。

使用条件:结构体中柔性数组成员前至少要有另一个成员。此外,使用 sizeof 计算该结构体的大小时,不包含柔性数组所占用的内存。

实现方式:通过 malloc 多分配一些空间来满足柔性数组的大小。这样做的好处在于,整个结构体和柔性数组的数据在物理内存上是连续的,这不仅减少了内存碎片的产生,还提高了 CPU 缓存命中率和访问速度。

第5步:使用柔性数组构造对象

以下示例展示了一个包含整型标记和无限制整型数组的结构体分配过程:

#include <stdio.h>
#include <stdlib.h>
struct S
{
    int n;
    int arr[]; // 柔性数组成员
};
int main()
{
    // 分配 S 的大小加上 5 个整数的空间
    struct S* ps = (struct S*)malloc(sizeof(struct S) + 5 * sizeof(int));
    if (ps == NULL)
    {
        return 1;
    }
    // …… 各种对 ps 的操作 ……
    // 一次性释放整个结构体及其柔性数组
    free(ps);
    ps = NULL;
    return 0;
}
以上就是关于C语言深入讲解动态内存分配函数的详细内容,更多关于C语言动态内存分配的资料请关注本站其它相关文章!
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表