通过创建变量或数组可以开辟内存,然而像这样的开辟方法所开辟的内存大小是固定的,一经创建,其大小无法改变,像这样的内存属于静态内存;相对于静态内存,也存在有动态内存,顾名思义,其内存大小可以根据需要进行动态的增长或缩小。
动态内存的开辟
C语言中可以通过malloc函数与calloc函数开辟动态内存,二者作用类似,功能略不相同。
动态内存使用完毕后需要释放,不然会造成内存泄漏,内存泄漏到一定程度,可能导致程序崩溃,因此每次使用完动态内存都需要进行释放,而C语言中专门用来释放动态内存申请的空间函数为free函数。
所有的动态内存相关函数均定义于<stdlib.h>的头文件中。
malloc函数
定义:
void* malloc( size_t size );
功能:分配 size 字节的未初始化内存。
若分配成功,则返回为任何拥有基础对齐的对象类型对齐的指针。
若 size 为零,则 malloc 的行为视编译器而定。
参数
size - 要分配的字节数
返回值
成功时,返回指向新分配内存的指针。为避免内存泄漏,必须用 free() 或 realloc() 解分配返回的指针。
失败时,返回空指针。
举例:
#include<stdlib.h>
int main()
{
int* p1 = (int*)malloc(3 * sizeof(int));//分配最多可以容纳三个整型的空间
char* p2 = (char*)malloc(10 * sizeof(char));//分配10个字节的空间
//p1与以下等价,均分配12个字节的空间
int* p3 = malloc(3 * sizeof(int));
int* p4 = malloc(sizeof(int[3]));
int* p5 = malloc(3 * sizeof * p5);
//使用完后释放
free(p1);
free(p2);
free(p3);
free(p4);
free(p5);
p1 = NULL;
p2 = NULL;
p3 = NULL;
p4 = NULL;
p5 = NULL;
return 0;
}
使用free函数释放掉内存后,要及时将指针置空,否则原指针成为野指针。
calloc函数
定义:
void* calloc( size_t num, size_t size );
功能:
为 num 个对象的数组分配内存,并初始化所有分配存储中的字节为零。
若分配成功,会返回指向分配内存块最低位(首位)字节的指针,它为任何类型适当地对齐。
若 size 为零,行为视编译器而定。
参数
num - 对象数目
size - 每个对象的大小
返回值
成功时,返回指向新分配内存的指针。为避免内存泄漏,必须用 free() 或 realloc() 解分配返回的指针。
失败时,返回空指针。
可以看到malloc函数与calloc函数的作用是相同的,不过calloc函数直接初始化了开辟的内存空间,而malloc函数需要另外再写代码初始化(如果需要的话);此外,二者的参数部分不相同,malloc函数直接开辟一定字节的空间,而calloc函数则需要告知变量类型的大小和数目。
realloc()函数
定义:
void *realloc( void *ptr, size_t new_size );
功能:
重新分配给定的内存区域。它必须是之前为 malloc() 、 calloc() 或 realloc() 所分配,并且仍未被 free 或 realloc 的调用所释放。否则,结果未定义。
参数
ptr - 指向需要重新分配的内存区域的指针
new_size - 数组的新大小(字节数)
返回值
成功时,返回指向新分配内存的指针。返回的指针必须用 free() 或 realloc() 释放。原指针 ptr 被释放。
失败时,返回空指针。原指针 ptr 保持有效,并需要通过 free() 或 realloc() 释放。
realloc函数的重新分配按以下二者之一执行:
a) 当前内存段后面剩余空间充足,则直接扩展这块内存,并且返回原指针。
b) 当前内存段后面剩余空间不足,则分配一个大小为 new_size 字节的新内存块,并复制大小等于新旧大小中较小者的内存区域,然后释放旧内存块。
若无足够内存,则不释放旧内存块,并返回空指针,原指针仍有效。
若 ptr 为 NULL ,则行为与调用 malloc(new_size) 相同。
若 new_size 为零,则相当于free(ptr)。
举例:
int* p1 = (int*)malloc(3 * sizeof(int));//分配最多可以容纳三个整型的空间
int* ptr = (int*)realloc(p1, 5 * sizeof(int));//重新分配五个整型的空间,原空间失效
if (ptr != NULL)
p1 = ptr;
//使用完释放
free(ptr);
ptr = NULL;
动态内存函数常见错误
在使用内存函数开辟空间时,有许多常见错误,例如对NULL指针的解引用、多次free()同一块内存、free()非动态开辟的内存等。
错误1:(对NULL的解引用操作)
int* p1 = (int*)malloc(3 * sizeof(int));//分配最多可以容纳三个整型的空间
int* ptr = (int*)realloc(p1, 5 * sizeof(int));
p1 = ptr;
//使用完释放
free(ptr);
ptr = NULL;
return 0;
在realloc某一内存块时,如果重分配失败,返回空指针,这时原指针所指向的内容并没有被释放,而此时没有判断ptr是否为NULL,直接把ptr赋给p1,则导致原来的数据丢失,内存泄漏。
因此在使用内存函数后,必要的判断必不可少,即每次开辟空间后要判断指针是否为NULL。
错误2:(多次free()同一块内存)
int* p1 = (int*)malloc(3 * sizeof(int));//分配最多可以容纳三个整型的空间
int* ptr = (int*)realloc(p1, 0);
p1 = ptr;
//使用完释放
free(ptr);
ptr = NULL;//二次free同一块内存
return 0;
由于realloc(p1, 0)相当于free(p1),这时p1所指向的内容已经还给操作系统,不在归操作者使用,因此不能再对其free,这里free(ptr)相当于free了两次,可能造成程序崩溃。
而对于free(NULL),不会执行任何操作。
错误3:(free()非动态开辟的内存块)
int a[5] = { 1,2,3,4,5 };
int *p = a;
free(p);
p = NULL;
p所指向的内容属于静态内存,而free()是专门服务于动态内存的函数,因此强行free()非动态内存必然造成程序的崩溃。
此外动态内存忘记释放并且置空以及越界访问也是其常见错误之一。
例如:输出如下程序结果是什么?
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
你一通思考后,发现好像并没有什么问题,在编译器上执行也没问题,于是坚定的得出输出结果为:hello。
可是它存在内存泄漏!