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

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

大家好,又见面了,我是全栈君。

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

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

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

转载于:https://my.oschina.net/idreamo/blog/809358

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/108716.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 不就是Java编程嘛,来来来

    点击上方☝Java编程技术乐园,轻松关注!及时获取有趣有料的技术文章做一个积极的人编码、改bug、提升自己我有一个乐园,面向编程,春暖花开! …

  • 2022保密教育线上培训考试 01[通俗易懂]

    2022保密教育线上培训考试 01[通俗易懂]试题1单选题1.机关、单位应当严格按照经过批准的范围对外提供涉密资料,并与外方签订(),限定涉密资料的使用和知悉范围。正确答案:B.保密协议2.按照公职人员政务处分法有关规定,有()行为造成不良后果或者影响的,予以警告、记过或者记大过;情节较重的,予以降级或者撤职;情节严重的,予以开除。正确答案:D.以上都正确3.下列关于涉密载体销毁的说法错误的是()。正确答案:B.涉密载体销毁的登记、审批记录无须保存4.保密期限是对国家秘密采取保密措施的时间要求。保密期限包括的形式有()。正

  • pycharm如何远程连接服务器_py服务端软件

    pycharm如何远程连接服务器_py服务端软件通过pycharm远程连接服务器首先确定你连接服务器的方式软件准备验证软件是否安装成功pycharm远程连接服务器上传自己的project到Ubuntu上传完以后,开始给自己的项目配置服务器的python解释器如何使用路由器,开启外网映射通过路由器的底部的网址进入管理员页面选择应用管理进入虚拟服务器在虚拟服务器中添加需要把内网映射到外网的IP地址查看自己映射出去的外网IP地址至此大功告成!!!您可以通过外网来访问您学校的服务器啦!首先确定你连接服务器的方式一般连接服务器需要服务器的ip地址,IP地址分为

  • 一文看懂风控模型所有

    一文看懂风控模型所有【与数据同行】已开通综合、数据仓库、数据分析、产品经理、数据治理及机器学习六大专业群,加微信号frank61822702为好友后入群。新开招聘交流群,请关注【与数据同行】公众号,后台回…

  • 添加音乐的HTML标签是,添加背景音乐的html标签是哪个[通俗易懂]

    添加音乐的HTML标签是,添加背景音乐的html标签是哪个[通俗易懂]添加背景音乐的html标签是哪个发布时间:2020-11-1710:26:08来源:亿速云阅读:120作者:小新了解添加背景音乐的html标签是哪个?这个问题可能是我们日常学习或工作经常见到的。希望通过这个问题能让你收获颇深。下面是小编给大家带来的参考内容,让我们一起来看看吧!添加背景音乐的html标签是,bgsound是用以插入背景音乐,但只适用于IE,在netscape和firefox中并不…

  • GOF23-创建型:简单工厂模式

    GOF23-创建型:简单工厂模式

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号