当前位置:嗨网首页>书籍在线阅读

17-分配内存_malloc()和free()

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

12.4 分配内存: malloc()free()

我们前面讨论的存储类别有一个共同之处:在确定用哪种存储类别后,根据已制定好的内存管理规则,将自动选择其作用域和存储期。然而,还有更灵活的选择,即用库函数分配和管理内存。

首先,回顾一下内存分配。所有程序都必须预留足够的内存来存储程序使用的数据。这些内存中有些是自动分配的。例如,以下声明:

float x;
char place[] = "Dancing Oxen Creek";

为一个 float 类型的值和一个字符串预留了足够的内存,或者可以显式指定分配一定数量的内存:

int plates[100];

该声明预留了100个内存位置,每个位置都用于存储 int 类型的值。声明还为内存提供了一个标识符。因此,可以使用 xplace 识别数据。回忆一下,静态数据在程序载入内存时分配,而自动数据在程序执行块时分配,并在程序离开该块时销毁。

C能做的不止这些。可以在程序运行时分配更多的内存。主要的工具是 malloc() 函数,该函数接受一个参数:所需的内存字节数。 malloc() 函数会找到合适的空闲内存块,这样的内存是匿名的。也就是说, malloc() 分配内存,但是不会为其赋名。然而,它确实返回动态分配内存块的首字节地址。因此,可以把该地址赋给一个指针变量,并使用指针访问这块内存。因为 char 表示1字节, malloc() 的返回类型通常被定义为指向 char 的指针。然而,从ANSI C标准开始,C使用一个新的类型:指向 void 的指针。该类型相当于一个“通用指针”。 malloc() 函数可用于返回指向数组的指针、指向结构的指针等,所以通常该函数的返回值会被强制转换为匹配的类型。在ANSI C中,应该坚持使用强制类型转换,提高代码的可读性。然而,把指向 void 的指针赋给任意类型的指针完全不用考虑类型匹配的问题。如果 malloc() 分配内存失败,将返回空指针。

我们试着用 malloc() 创建一个数组。除了用 malloc() 在程序运行时请求一块内存,还需要一个指针记录这块内存的位置。例如,考虑下面的代码:

double * ptd;
ptd = (double *) malloc(30 * sizeof(double));

以上代码为30个 double 类型的值请求内存空间,并设置 ptd 指向该位置。注意,指针 ptd 被声明为指向一个 double 类型,而不是指向内含30个 double 类型值的块。回忆一下,数组名是该数组首元素的地址。因此,如果让 ptd 指向这个块的首元素,便可像使用数组名一样使用它。也就是说,可以使用表达式 ptd[0] 访问该块的首元素, ptd[1] 访问第2个元素,以此类推。根据前面所学的知识,可以使用数组名来表示指针,也可以用指针来表示数组。

现在,我们有3种创建数组的方法。

  • 声明数组时,用常量表达式表示数组的维度,用数组名访问数组的元素。可以用静态内存或自动内存创建这种数组。
  • 声明变长数组(C99新增的特性)时,用变量表达式表示数组的维度,用数组名访问数组的元素。具有这种特性的数组只能在自动内存中创建。
  • 声明一个指针,调用 malloc() ,将其返回值赋给指针,使用指针访问数组的元素。该指针可以是静态的或自动的。

使用第2种和第3种方法可以创建动态数组(dynamic array)。这种数组和普通数组不同,可以在程序运行时选择数组的大小和分配内存。例如,假设 n 是一个整型变量。在C99之前,不能这样做:

double item[n]; /* C99之前:n不允许是变量 */

但是,可以这样做:

ptd = (double *) malloc(n * sizeof(double)); /* 可以 */

如你所见,这比变长数组更灵活。

通常, malloc() 要与 free() 配套使用。 free() 函数的参数是之前 malloc() 返回的地址,该函数释放之前 malloc() 分配的内存。因此,动态分配内存的存储期从调用 malloc() 分配内存到调用 free() 释放内存为止。设想 malloc()free() 管理着一个内存池。每次调用 malloc() 分配内存给程序使用,每次调用 free() 把内存归还内存池中,这样便可重复使用这些内存。 free() 的参数应该是一个指针,指向由 malloc() 分配的一块内存。不能用 free() 释放通过其他方式(如,声明一个数组)分配的内存。 malloc()free() 的原型都在 stdlib.h 头文件中。

使用 malloc() ,程序可以在运行时才确定数组大小。如程序清单12.14所示,它把内存块的地址赋给指针 ptd ,然后便可以使用数组名的方式使用 ptd 。另外,如果内存分配失败,可以调用 exit() 函数结束程序,其原型在 stdlib.h 中。 EXIT_FAILURE 的值也被定义在 stdlib.h 中。标准提供了两个返回值以保证在所有操作系统中都能正常工作: EXIT_SUCCESS (或者,相当于 0 )表示普通的程序结束, EXIT_FAILURE 表示程序异常中止。一些操作系统(包括UNIX、Linux和Windows)还接受一些表示其他运行错误的整数值。

程序清单12.14  dyn_arr.c 程序

/* dyn_arr.c -- 动态分配数组 */
#include <stdio.h>
#include <stdlib.h> /* 为 malloc()、free()提供原型 */
int main(void)
{
     double * ptd;
     int max;
     int number;
     int i = 0;
     puts("What is the maximum number of type double entries?");
     if (scanf("%d", &max) != 1)
     {
          puts("Number not correctly entered -- bye.");
          exit(EXIT_FAILURE);
     }
     ptd = (double *) malloc(max * sizeof(double));
     if (ptd == NULL)
     {
          puts("Memory allocation failed. Goodbye.");
          exit(EXIT_FAILURE);
     }
     /* ptd 现在指向有max个元素的数组 */
     puts("Enter the values (q to quit):");
     while (i < max && scanf("%lf", &ptd[i]) == 1)
          ++i;
     printf("Here are your %d entries:\n", number = i);
     for (i = 0; i < number; i++)
     {
          printf("%7.2f ", ptd[i]);
          if (i % 7 == 6)
               putchar('\n');
     }
     if (i % 7 != 0)
          putchar('\n');
     puts("Done.");
     free(ptd);
     return 0;
}

下面是该程序的运行示例。程序通过交互的方式让用户先确定数组的大小,我们设置数组大小为 5 。虽然我们后来输入了6个数,但程序也只处理前5个数。

What is the maximum number of entries?
5
Enter the values (q to quit):
20 30 35 25 40 80
Here are your 5 entries:
  20.00 30.00 35.00 25.00 40.00
Done.

该程序通过以下代码获取数组的大小:

if (scanf("%d", &max) != 1)
{
     puts("Number not correctly entered -- bye.");
     exit(EXIT_FAILURE);
}

接下来,分配足够的内存空间以存储用户要存入的所有数,然后把动态分配的内存地址赋给指针 ptd

ptd = (double *) malloc(max * sizeof (double));

在C中,不一定要使用强制类型转换 (double * ) ,但是在C++中必须使用。所以,使用强制类型转换更容易把C程序转换为C++程序。

malloc() 可能分配不到所需的内存。在这种情况下,该函数返回空指针,程序结束:

if (ptd == NULL)
{
     puts("Memory allocation failed. Goodbye.");
     exit(EXIT_FAILURE);
}

如果程序成功分配内存,便可把 ptd 视为一个有 max 个元素的数组名。

注意, free() 函数位于程序的末尾,它释放了 malloc() 函数分配的内存。 free() 函数只释放其参数指向的内存块。一些操作系统在程序结束时会自动释放动态分配的内存,但是有些系统不会。为保险起见,请使用 free() ,不要依赖操作系统来清理。

使用动态数组有什么好处?从本例来看,使用动态数组给程序带来了更多灵活性。假设你已经知道,在大多数情况下程序所用的数组都不会超过100个元素,但是有时程序确实需要10000个元素。要是按照平时的做法,你不得不为这种情况声明一个内含10000个元素的数组。基本上这样做是在浪费内存。如果需要10001个元素,该程序就会出错。这种情况下,可以使用一个动态数组调整程序以适应不同的情况。