找回密码
 register

QQ登录

只需一步,快速开始

查看: 227|回复: 0

[C语言] 分配内存:malloc()和free()

[复制链接]

[C语言] 分配内存:malloc()和free()

[复制链接]
  • 打卡等级:热心大叔
  • 打卡总天数:149
  • 打卡月天数:22
  • 打卡总奖励:148
  • 最近打卡:2025-03-28 00:15:39
Waylee

主题

0

回帖

1万

积分

仙帝

积分
18813
Waylee 2025-2-12 10:05 | 显示全部楼层 |阅读模式 | Safari| iPhone iOS 18.3.0

马上注册,查看网站隐藏内容!!

您需要 登录 才可以下载或查看,没有账号?register

×

我们前面讨论的存储类别有一个共同之处:在确定用哪种存储类别后,根据已制定好的内存管理规则,将自动选择其作用域和存储期。然而,还有更灵活地选择,即用库函数分配和管理内存。
首先,回顾一下内存分配。所有程序都必须预留足够的内存来储存程序使用的数据。这些内存中有些是自动分配的。例如,以下声明:

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

为一个float类型的值和一个字符串预留了足够的内存,或者可以显式指定分配一定数量的内存:
int plates[100];
该声明预留了100个内存位置,每个位置都用于储存int类型的值。声明还为内存提供了一个标识符。因此,可以使用x或place识别数据。回忆一下,静态数据在程序载入内存时分配,而动态数据在程序执行块时分配,并在程序离开该块时销毁。
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个元素,该程序就会出错。这种情况下,可以使用一个动态数组调整程序以适应不同的情况。

12.4.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() */
}

第1次调用gobble()时,它创建了指针temp,并调用malloc()分配了16000字节的内存(假设double为8字节)。假设如代码注释所示,遗漏了free()。当函数结束时,作为自动变量的指针temp也会消失。但是它所指向的16000字节的内存却仍然存在。由于temp指针已被销毁,所以无法访问这块内存,它也不能被重复使用,因为代码中没有调用free()释放这块内存。
第2次调用gobble()时,它又创建了指针temp,并调用malloc()分配了16000字节的内存。第1次分配的16000字节内存已不可用,所以malloc()分配了另外一块16000字节的内存。当函数结束时,该内存块也无法被再访问和再使用。
循环要执行1000次,所以在循环结束时,内存池中有1600万字节被占用。实际上,也许在循环结束之前就已耗尽所有的内存。这类问题被称为内存泄漏(memory leak)。在函数末尾处调用free()函数可避免这类问题发生。

您需要登录后才可以回帖 登录 | register

本版积分规则

雪舞知识库 | 浙ICP备15015590号-1 | 萌ICP备20232229号|浙公网安备33048102000118号 |天天打卡

GMT+8, 2025-3-29 18:41 , Processed in 0.161771 second(s), 6 queries , Redis On.

Powered by XueWu Licensed

Copyright © Tencent Cloud.

快速回复 返回顶部 返回列表