C Primer Plus 第12章 12.6 分配内存:malloc()和free()

这一生的挚爱 提交于 2019-12-03 21:40:06

首先,回顾一些有关内存分配的事实。所有的程序都必须留出足够内存来存储它们使用的数据。一些内存分配是自动完成的。例如,可以这样声明:

float x;

char place[]="Dancing oxen creek";

于是,系统将留出存储float或字符串的足够内存空间,您也可以更明确地请求确切数量的内存:

int plates[100];

这个声明留出100个内存位置,每个位置可存储一个int值。在所有这些情形中,声明同时给出了内存的标识符,因此您可以使用x或place来标识数据。

C的功能还不止这些。可以在程序运行时分配 更多的内存。主要工具是函数malloc(),它接受一个参数:所需内存字节数。然后,malloc()找到可用内存中一个大小合适的块。内存是匿名的,也就是malloc()分配了内存,但没有为它指定名字。然而,它却可以返回那块内存第一个字节的地址。因此,您可以把那个地址赋给一个指针变量,并使用该指针来访问那块内存。因为char代表一个字节,所以传统上曾将malloc()定义为指向char的指针类型。然而,ANSI 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]来访问第二个元素,依此类推。正如前面所学,可以在指针符号中使用数组名,也可以在数组符号中使用指针。

现在,创建一个数组有三个方法:

1、声明一个数组,声明时用常量表达式指定数组,然后可以用数组名访问数组元素。

2、声明一个变长数组,声明时用变量表达式指定数组,然后用数组名来访问数组元素(回忆下,这是C99的特性)。

3、声明一个指针,调用malloc(),然后使用该指针来访问数组元素。

使用第二种或第三种方法可以做一些用普通的数组声明做不到的事。创建一个动态数组(dynamic arry)即一个在程序运行时才分配内存并可在程序运行时选择大小的数组。例如,假定n是一个整数变量。在c99之前,不能这样做:

double item[n];  /*如果n 是一个变量,C99之前不允许这样做*/

然而,即使在C99之前的编译器中,也可以这样做:

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

这行得通,而且正如您看到的那样,这样做比使用一个变长数组更加灵活。

一般地,对应每个malloc()调用,应该调用一次free()函数free()的参数是先前malloc()返回的地址,它释放先前分配的内存。这样,所分配内存的持续时间从调用malloc()分配内存开始,到调用 free()释放内存以供再使用为止。设想malloc()和free()管理着一个内存池。每次调用 malloc()分配内存给程序使用,每次调用free()将内存归还到池中,使内存可被再次使用。free()的参数应是一指针,指向由malloc()分配的内存块;不能使用free()来释放通过其他方式(例如声明一个数组)分配的内存在头文件stdlib.h中有malloc()和free()的原型。

通过使用malloc(),程序可以在运行时决定需要多大的数组并创建它。程序清单12.14举例证明了这一可能。它把内存块地址赋给指针ptd,接着以使用数组名的方式使用ptd。程序还调用了exit()函数。该函数的原型在stdlib.h中,用来在内存分配失败时结束程序。值EXIT_FAILURE也在这个头文件中定义。标准库提供了两个保证能够在所有操作系统下工作的返回值:EXIT_SUCCESS(或者等同于0)指示程序正常终止,EXIT_FAILURE指示程序异常终止。

程序清单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?");
    scanf("%d",&max);
    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;
}

运行示例:

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.

来看一下代码。程序通过下列几行获取所需的数组的大小:

puts ("What is the maximum number of type double entries?");

scanf("%d",&max);

接着,下面的行分配对于存放所请求数目的项来说足够大的内存,并将该内存块的地址赋给指针ptd:

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

在C中类型指派(double *)是可选的,而在C++中必须有,因此使用类型指派将C移植到C++更容易。

malloc()可能无法获得所需数量的内存。在那种情形下,函数返回空指针,程序终止。

if(ptd == NULL)

{

    puts("Memory allocation failed.Goodbye.");

    exit(EXIT_FAILTURE);

}

如果成功地分配了地址,程序将把ptd视为一个具有max个元素的数组的名字。

在这个特定的例子中,使用free()不是必须的,因为在程序终止后所有已分配的内存都将被释放。然而在一个更加复杂的程序中,能够释放并再利用内存将是重要的。

使用动态数组将获得什么?主要是获得了程序的灵活性。您可以使用动态数组来使程序适应不同的情形。

12.6.1   free()的重要性

在编译程序时,静态变量的数量是固定的,在程序运行时也不改变。自动变量使用的内存数量在程序运行时自动增加或者减少。但被分配的内存所使用的内存数量只会增加,除非您记得使用free()。例如,假定有一个如下代码勾勒出的函数,它创建一个数组的临时拷贝:

...
int main()
{
double glad[2000];
int i;
...
for(i=0; i<1000; i++)
gobble(glad,2000);
...
}
void gobble(double ar[],int n)
{
double * temp = (double *) malloc(n*sizeof(double));
...
/*free(temp);  //忘记使用free()*/
}

假定我们如暗示的那样没有使用freee()。当函数终止时,指针temp作为一个自动变量消失了。但它所指向的16000个字节的内存仍旧存在。我们无法访问这些内存,因为地址不见了。由于没有调用free(),不可以再使用它了。

第二次调用gobble(),它又创建了一个temp,再次使用malloc()分配 16000个字节的内存。第一次16000字节的块已不可用,因此malloc()不得不再找一个16000字节的块。当函数终止时,这个内存块也无法访问,不可再利用。

但循环执行 1000次,因此在循环最终结束时,已经有1600万字节的内存从内存池中移走。事实上,到达这一步之前,程序很可能已经内存溢出了。这类问题被称为“内存泄漏(memory leak),可以通过在函数末尾处调用 free()防止该问题出现。

12.6.2  函数calloc()

内存分配还可以使用calloc()。典型的应用如下:

long * newmem;

newmem = (long *)calloc(100,sizeof(long));

与malloc()类似,calloc()在ANSI C以前的版本中返回一个char指针,在ANSI 中返回一个void指针。如果要存储不同类型,应该使用类型指派运算符。这个新函数接受两个参数,都应是无符号的整数(在ANSI 中 是SIZE_T类型)。第一个参数是所需内存单元的数量,第二个参数是每个单元以字节计的大小。在这里,long使用4个字节,因此这一指令建立了100个4字节单元,总共使用400个字节来存储。

使用sizeof(long)而不是使用4使代码更容易移植。它可以其他系统中运行, 这些系统 中long不是4字节而是别的大小 。

函数calloc()还有一个特性:它将块中的全部位都置为0(然而要注意,在某些硬件系统中,浮点值0不是用全部位为0来表示的)。

函数free()也可以用来释放由calloc()分配的内存。

动态内存分配是很多高级编程技巧的关键。在17章“高级数据表示”中我们将研究一些。你自己的C库可能提供了其他内存管理函数,有些可移植,有些不可以。您可能应该抽时间看一下。

12.6.3  动态内存分配与变长数组

变长数组(Variable-Length Array,VLA)与malloc()在功能上有些一致。例如,它们都可以用来创建一个大小在运行时决定的数组:

int vlamal()
{
    int n;
    int * pi;
    scanf("%d",&n);
    pi = (int *) malloc(n*sizeof(int));
    int ar[n];    //变长数组
    pi[2] = ar[2] =-5;
    ...
}

一个区别 在于VLA是自动存储的。自动存储的结果之一就是VLA所用内存空间在运行完定义部分之后 会自动释放。在本例中,就是函数vlamal()终止的时候。因此不必使用free()。另一方面,使用由malloc()创建的数组不必局限在一个函数中。例如,函数可以创建一个数组并返回指针,供调用该函数的函数访问。接着,后者可以在它结束时调用free()。free()可以使用不同于malloc()指针的指针变量,必须一致的是指针中存储的地址。

VLA对多维数组来说更方便。您可以使用malloc()来定义一个二维数组,但语法很麻烦。如果编译器不支持VLA特性,必须固定一维的大小,正如下面的函数调用 :

int n=5;
int m=6;
int ar2[n][m];  //n*m的变长数组
int (* p2)[6]; //在C99之前可以使用
int (* p3)[m]; //要求变长数组支持
p2 = (int (*)[6])malloc(n*6*sizeof(int));  //n*6数组
p3 = (int (*)[m])malloc(n*m*sizeof(int));  //n*m数组
//上面的表达式也要求变长数组支持
ar2[1][2] = p2[1][2] = 12;

有必要查看一下指针声明。函数malloc()返回一个指针,因此p2必须是适当类型的指针。下面的声明:

int (*p2)[6];  //在C99之前可以使用

表明p2指向一个包含6个int值的数组。这意味着p2[i]将被解释为一个由6个整数构成的元素,p2[i][j]将是一个int 值。

第二个指针声明使用变量来指定p3所指数组的大小。这意味着p3将被看作一个指向VLA的指针,这正是代码不能在C90标准中运行的原因。

12.6.4  存储类与动态内存分配

您可能正在为存储类和动态内存分配之间的联系感到疑惑。我们来看一个理想模型。可以认为程序将它的可用内存分成 了三个独立的部分:一个是具有外部链接的、具有内部链接的以及具有空链接的静态变量的;一个是自动变量的;另一个是动态分配的内存的。

在编译时就已经知道 了静态存储时期存储类变量所需的内存数量,存储在这一部分的数据在整个程序运行期间都可以用。这一类型的每个变量在程序开始已经存在,到程序结束时终止

然而,一个自动变量在程序进入包含该变量定义的代码产生,在退出这一代码块时终止 。因此,伴随着程序对函数的调用和终止,自动变量使用的内存数量也在增加和减少。典型的,将这一部分内存处理为一个堆栈。这意味着在内存中,新变量在创建时按顺序加入,在消亡时按相反顺序移除。

动态分配的内存在调用malloc()或相关函数时产生,在调用free()时释放。由程序员而不是一系列固定的规则控制内存持续时间,因此内存块可在一个函数中创建,而在另一个函数中释放。由于这一点,动态内存分配所用的内存部分可能变成碎片状,也就是说,在活动的内存块之间散布着未使用的字节片。

不管怎样,使用动态内存往往导致进程比使用堆栈内存慢。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!