一、动态分配内存的概述
动态分配内存是指在程序运行时,根据需要动态地申请和释放内存空间。
在学习数组时,数组的长度是预先定义好的,在整个程序中固定不变。但是在实际的编程中,往往会发生一种情况,即所需的内存空间取决于实际输入的数据,而无法预先确定 。为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态的分配内存空间,也可把不再使用的空间回收再次利用。
动态分配内存就是在堆区开辟空间。
二、静态分配、动态分配
1. 静态分配
(1) 在程序编译时就确定了内存空间的大小和位置,如 inta[10]
。
(2) 必须事先知道所需空间的大小。
(3) 分配在栈区或全局变量区,一般以数组的形式。
(4) 按计划分配。
2. 动态分配
(1) 在程序运行过程中,根据需要大小自由分配所需空间。
(2) 按需分配。
(3) 分配在堆区,一般使用特定的函数进行分配。\
三、动态分配函数
1. malloc 函数
头文件:#include<stdlib.h>
函数原型: void *malloc(unsigned int size);
功能说明:
在内存的动态存储区(堆区)中分配一块长度为 size
字节的连续区域,用来存放类型说明符指定的类型。
函数返回 void*
指针,使用时必须做相应的强制类型转换,分配的内存空间内容不确定,一般使用 memset
初始化。
返回值:
分配空间的首地址(分配内存成功)
NULL
(分配内存失败)
注意:
(1) 在调用 malloc 之后,一定要判断一下,是否申请内存成功。
(2) 多次 malloc 申请的内存空间不一定是连续的。
(3) 在使用 malloc 开辟空间时,需要保存新开辟空间的首地址。
(4) 因为 malloc 函数的返回类型是 void*,所以在调用函数时需要根据接收者的类型进行强制类型转换。
#include <stdio.h>
char *fun()
{
// char str[100] = "hello world"; // 在栈区开辟空间,当前代码段结束后,空间释放。
// 定义一个全局静态变量,当前代码段结束后,空间不释放,直到程序结束才释放。若临时使用,不建议使用静态全局区
// static char str[100] = "hello world";
// 堆区开辟空间,手动申请,手动释放,更加灵活
// 使用malloc函数时,一般需要强转
char *str = (char *)malloc(100 * sizeof(char));
str[0] = 'h';
str[1] = 'e';
str[2] = 'l';
str[3] = 'l';
str[4] = 'o';
str[5] = '\0';
return str;
}
int main()
{
char *p;
p = fun();
printf("p = %s\n", p);
return 0;
}
执行结果:
p = hello
2. free 函数:释放内存
头文件:#include<stdlib.h>
函数原型:void free(void *ptr)
函数说明:free 函数释放 ptr 指向的内存空间。
功能:释放动态分配的内存空间(堆区空间),使得这部分内存可以重新被使用或者返回给系统。
参数:ptr 指向动态分配的内存空间。
返回值:无
注意:
(1) free 函数只能释放堆区空间,其他区域的空间无法使用 free
(2) ptr 指向的内存必须是 malloc, calloc, relloc 动态申请的内存
(3) free 不能只释放一部分空间
(4) 指针置空: 释放内存后,建议将指针设置为 NULL
,以防止程序误用已释放的内存空间(防止野指针)。例如:ptr = NULL;
。
3. calloc 函数:申请指定大小内存
头文件:#include<stdlib.h>
函数原型:``void *calloc(size_t num, size_t size);`
接收参数:
num:要分配的元素数量。
size:每个元素的大小(以字节为单位)。
功能:在内存的堆区中,一次性申请 num 块大小相同的内存空间,每块内存空间的大小为 size 个字节的连续区域。
size_t 是 C 标准库 <stddef.h> 中定义的一种无符号整数类型。在动态内存分配函数中,参数和返回值通常使用 size_t 类型来表示要分配的内存大小或已分配的内存大小。
返回值:
申请空间的首地址(分配内存成功)
NULL(分配内存失败)
malloc 和 calloc 的区别:
(1) 接收参数数量不同:malloc 接收 1 个参数;calloc 接收 2 个参数
(2) malloc 申请的内存中存放的内容是随机的;calloc 申请的内存中存放的内容是 0
调用方法:
char *p = (char *)calloc(3, 100);
上述代码在堆区申请了 3 块内存空间,每块内存空间的大小为 100 个字节,即申请了 300 个字节的连续空间。
4. realloc 函数:重新申请内存
调用 malloc 和 calloc 时,函数单次申请的内存是连续的,两次申请的两块内存不一定连续。
有些时候有这种需求:
已经用 malloc 或 calloc 申请了一块内存,还想在已申请内存的基础上挨着申请内存;
已经用 malloc 或 calloc 申请了一块内存,想释放一部分内存。
为了解决些需求,发明了 realloc 函数。
头文件:#include<stdlib.h>
函数原型:void *realloc(void *s, unsigned int newsize);
接收参数:
s:指向之前动态分配的内存空间的指针。
newsize:新的内存空间大小(以字节为单位)。
返回值:
重新分配后的内存空间首地址(内存重新分配成功)
NULL(内存重新分配失败)
扩大或缩小内存空间:如果新的内存空间大小大于原来的大小,则 realloc()
函数会尝试扩大内存空间;如果新的内存空间大小小于原来的大小,则 realloc()
函数会尝试缩小内存空间。如果新的内存空间大小与原来的大小相同,则 realloc()
函数不会实际做任何操作,直接返回原指针。
扩大空间:
char *p;
p = (char *)malloc(100);
p = (char *)realloc(p, 150); // p 指向的内存空间大小为 150 字节。
缩小空间:
char *p;
p = (char *)malloc(100);
p = (char *)realloc(p, 50); // p 指向的内存空间大小为 50 字节。
后面 50 个字节的空间被释放。
注意:malloc, calloc, realloc 动态申请的内存,只有在 free 或程序结束时才被释放。
四、内存泄漏
内存泄漏指的是程序中动态分配的内存空间在不再需要时未被正确释放或者释放失败的情况。换句话说,内存泄漏就是程序中分配的内存空间一直占用着,但是却没有被程序使用,也无法被程序释放,从而导致系统的内存资源浪费。
内存泄漏例 1 :
int main()
{
char *p;
p = (char *)malloc(100); // 分配了 100 个字节的内存空间,并将其地址赋值给指针 p
p = "hello world"; // 将字符串常量 "hello world" 的地址赋值给指针 p
return 0;
}
p = "hello world";
这个操作导致指针 p 不再指向之前动态分配的内存空间(堆区),而是指向了字符串常量的地址。由于没有手动释放之前动态分配的内存空间,这部分内存空间就会成为不可访问和不可释放的内存,从而导致内存泄漏。
内存泄漏例 2 :
void fun()
{
char *p;
p = (char *)malloc(100);
}
int main()
{
fun();
fun();
return 0;
}
每调用一次 fun,就泄露 100 字节。
内存泄漏解决方案 1:
void fun()
{
char *p;
p = (char *)malloc(100);
free(p); // 在函数内释放内存空间
}
int main()
{
fun();
fun();
return 0;
}
内存泄漏解决方案 2:
void fun()
{
char *p;
p = (char *)malloc(100);
return p; // 返回 p
}
int main()
{
q = fun();
free(q); // 调用一次函数,释放一次内存空间
q = NULL; // 防止野指针
q = fun();
q = NULL;
free(q);
return 0;
}