C 语言中的动态内存分配


2021年8月23日, Learn eTutorial
2214

在本教程中,您将学习如何有效地管理内存。您将涵盖 C 语言中内存分配的类型、动态内存分配在 C 语言中的重要性,以及动态分配内存的不同方法,例如使用 malloccallocreallocfree

内存管理是 C 语言编程的一个重要方面。在编写程序时,我们必须确保分配的内存足以存储相关变量,并且空间不会被垃圾字节占用。基本上,C 语言编程通过 3 种不同的方式管理内存。它们是

  • 静态内存分配
  • 自动内存分配
  • 动态内存分配

到目前为止,我们已经学习了以静态模式为变量分配内存空间,但这会导致空间滥用。假设我们要存储一个名字,我们声明了一个 100 个字符的字符串来存储它。如果我们存储名字“Tim Cook”,只使用了 8 个字符。如果能有一种根据用户输入的数据来分配空间的方法,那就太好了。幸运的是,C 语言确实支持这种空间管理方式,通常称为“动态内存分配”。

动态内存分配

动态内存分配是一种在程序运行时,通过在需要时分配或释放内存来手动管理内存的方式。每当调用这些函数时,它们会从堆(内存区域)中获取内存,并在不需要时释放内存,以便可以重用。主要有两个函数执行动态内存分配:callocmalloc
 这里对它们做一个简短的介绍。 

C malloc()

术语“malloc”代表“内存分配”。C 语言中的 Malloc 用于在运行时分配指定字节数的内存。

语法

malloc() 函数的原型如下


ptr = (int*) malloc(10 * sizeof(int));
 

现在,这一切都取决于整数的属性。如果整数包含两个字节,则分配的空间将为 20 字节。如果整数的大小为 4 字节,则将保留 40 字节的空间。现在我们将尝试通过一个相关的例子来理解这个概念。请注意,我们包含了一个库文件 stdlib.h,因为这些函数是在这个特定的头文件中定义的。

示例


#include <string.h>
#include <stdlib.h>

main()
{
 char name[50];
 char *define;
 strcpy(name, "Thomas Edison ");
 define = malloc(50* sizeof(char));

 if (define == NULL)
 {
  fprintf(stderr, "Error - Memory allocation failed. \n");
 }
 else
 {
  strcpy(define, " Thomas Edison invented incandescent lights.");
 }

 printf("Name is %s\n", name);
 printf("Invention : %s\n", define);
}
 

输出

Name : Thomas Edison
Invention :  Thomas Edison invented incandescent lights.

因此,程序将内存分配的完全控制权交给了程序员。他可以根据情况的需要将值“50”更改为 20 或 100。在这个例子中,我们包含了一个字符串头文件来操作字符串,您将在接下来的教程中详细学习。

C calloc()

C 语言支持的另一个动态分配内存的库函数是 calloc() 函数,它被认为比 malloc 更好。函数 “calloc” 意为 “连续分配”,即对相邻单元进行串行和顺序分配。 

语法

此函数的原型声明是


pointer = (data_type*) calloc( memory_units, size_of_element);
 

这里 calloc() 接受两个参数

  • memory units 表示元素的数量
  • size_of_element 表示元素的大小

假设我们要为 10 个 float 单元分配空间。我们知道 float 变量占用 4 个字节的空间,因此 10 个单元意味着 40 个内存元素。所以它的内存分配看起来会是这样


pntr = (float*) calloc(10, sizeof(float));
 

示例


#include <stdio.h>

int main()
{
 int n;
 int *p;
 int i;

 printf("Enter the number of elements in array :\n");
 scanf("%d", &n);

 p = (int*) calloc(n, sizeof(int));
 printf("\nEnter the elements in array :\n");
 if (p == NULL)
 {
  printf("\n Error : Memory allocation failed");
  exit(1);
 }
 else
 {
  for (i = 0; i < n; i++)
  {
   scanf("%d", p + i);
  }

  printf("The array is :\n");
  for (i = 0; i < n; i++)
  {
   printf("%d\t", *(p + i));
  }
 }

 printf("\n\nThe array in reversed order is:\n");
 for (i = n - 1; i >= 0; i--)
 {
  printf("%d\t", *(p + i));
 }

 return 0;
}
 

输出


Enter the number of elements in array :
4

Enter the elements in the array :
2
5
6
9
The array is :
2       5       6       9

The array in reversed order is:
9       6       5       2

malloc 和 calloc 函数的主要区别

callocmalloc 的主要区别在于,前者分配多个相同大小的内存块。而后者则分配一个单一的内存块。Calloc 函数总是将分配的内存位初始化为零,而 malloc 函数则不会,因此包含垃圾值。Calloc 函数需要两个参数,而 malloc 只需要一个参数来分配内存。

调整大小和释放内存

当一个程序执行完成时,可能会出现两种情况。一种是你可能不再需要已分配的空间,另一种是我们必须重新定义大小(即增加或减少)。为了在运行时满足这些目的,C 语言中有两个独立的函数,分别是 free 和 reallocate。 

C realloc()

顾名思义,它重新分配用于数据存储的空间。换句话说,reallocate() 函数启用了修改先前分配的内存大小而不丢失旧数据的功能。

语法

realloc() 函数的原型是


pointer_name = realloc(pointer_name, new_size)
 

calloc() 类似,realloc() 也接受两个参数,一个参数提供先前使用 malloc()calloc() 分配的内存的第一个字节,第二个参数指定新的大小(可以比原始大小更大或更小)。考虑以下情况


pntr = (float*) calloc(10, sizeof(float));
 

假设我们想将已分配内存的大小增加 5 个浮点数,我们需要做的是为额外的 5 个浮点数单位重新分配空间。我们知道浮点变量占用 4 个字节的空间,因此 10 个单位加上额外的 5 个单位意味着 (15*4 = 60) 个内存元素。因此,它的内存重新分配将如下所示


pntr = (float*) realloc(pntr, 15*sizeof(float));
 

示例:

下面是一个演示 realloc() 工作原理的例子

#include <stdio.h>
#include <stdlib.h>
 main()
{
    int *p,s,ns,i;
    printf("Enter the size of the array :");
    scanf("%d", &s);

    p = (int*) malloc(s * sizeof(int));
    for(i= 0; i < s; ++i)
    {
      printf("\nAddress of each element in array at index %d ==> %u ",i,p+i);
    }

    printf("\n\nEnter new size of the data: ");
    scanf("%d", &ns);
    p = realloc(p, ns);
    for(i = 0; i < ns; ++i)
           printf("\nAddress of each element in array at index %d ==> %u ",i,p+i);
}


输出

Enter the size of the array :6

Address of each element in array at index 0 ==> 1731824
Address of each element in array at index 1 ==> 1731828
Address of each element in array at index 2 ==> 1731832
Address of each element in array at index 3 ==> 1731836
Address of each element in array at index 4 ==> 1731840
Address of each element in array at index 5 ==> 1731844

Enter the new size of the data: 2

Address of each element in array at index 0 ==> 1731824
Address of each element in array at index 1 ==> 1731828

C free()

这个函数的工作是释放已分配的内存。实际上,由 callocmalloc 等函数分配的内存在程序执行后不会自动变空。要显式地释放空间,我们只需调用 free() 函数,并将持有待释放地址的正确指针作为参数传入。 

语法

该函数的原型是


free (pointer);
 

演示 free() 的示例


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

int main()

{
 int *ptr;
 //creating memory allocation dynamically

 ptr = (int*) malloc(sizeof(int));
 printf("\n Memory allocated...");
 *ptr = 100; //assigning value  printf("\n ptr c *(ptr));
 free(ptr);
 printf("\n Memory is deallocated...\n ");  printf("ptr c *(ptr));
 return 0;

}
 

输出


Memory allocated... ptr c

Memory is deallocated... ptr c

在这个例子中,最初使用 malloc() 函数动态分配内存,并为其分配一个整数值。之后,我们使用 free() 函数释放了内存。

动态内存分配的优缺点

使用动态分配的主要优点是避免内存浪费。动态内存分配提供了根据需求更改内存大小的灵活性,并且适用于用户事先不知道内存大小的情况。

动态分配提出的唯一限制是需要显式释放。这意味着用户必须特别注意管理内存,在不再需要时释放空间,否则可能导致内存泄漏。