1、为什么存在动态内存分配
原因1:对于空间的需求会增大。
以数组为例: char arr[ ] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
数组在声明长度是固定的,内存中开辟的空间大小也固定。如果数组的空间不能满足我们的
需求,就需要使用动态内存分配
原因2:有些时候需要的空间大小在程序运行的时候我们才能知道。
这时数组编译时开辟空间的方式就不能满足需求。
原因3:可以调节内存的分配
如果一个数组开辟的空间过于大,但实际上用不了这么多,就需要使用动态内存分配缩减空
间的占用
2、动态内存函数的介绍
2.1 malloc
动态内存开辟函数malloc:
void* malloc(size_t size);
功能:分配size字节大小的内存块,返回指向块开头的指针。
- 新分配的内存块的内容没有初始化,保留不确定的值。
- 分配成功,返回指向函数分配的内存块的指针。
- 分配不成功,则返回空指针。
- 返回值是void* ,具体类型由使用者自己决定
- 如果size为0,则返回值取决于特定的库实现(它可能是空指针,也可能不是空指针),但返回的指针不应被解引用。
2.2 free
动态内存释放和回收函数free:
void free(void* ptr);
功能:以前通过调用malloc、calloc或realloc分配的内存块将被释放,使其可以再次用于下一次分
配。
- 如果ptr不指向用上述函数分配的内存块,则会导致未定义的行为。
- 如果ptr是空指针,则函数不执行任何操作。
- free函数使用后不会改变ptr本身的值,因此它仍然指向之前的位置,只是指向位置中的内容被释放,此时ptr 是野指针。
- 使用free函数后应及时将ptr赋为空,防止野指针的使用。
- 如果ptr不指向先前用malloc、calloc或realloc分配的内存块,并且不是空指针,则会导致未定义的行为。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* arr = (int*)malloc(sizeof(int) * 5); //为arr开辟5个int型空间
if (NULL == arr)//判断释放开辟成功
{
perror("malloc");//开辟失败则返回原因
return -1;
}
else
{
for (int i = 0; i < 5; i++)
{
*(arr + i) = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", *(arr + i));
}
//释放内存
free(arr);
arr = NULL;//是否有必要?
return 0;
}
}
2.3 calloc
void* calloc(size_t num, size_t size);
功能:为包含 num 个 size 大小元素的数组分配内存空间,并将内存中每个字节初始化为0
- calloc函数的有效结果是分配了一个0初始化的(num*size)字节的内存块。
- 分配成功,返回指向函数分配的内存块的指针。
- 分配不成功,则返回空指针。
- 如果size为0,则返回值取决于特定的库实现(它可能是空指针,也可能不是空指针),但返回的指针不应被解引用。
#include <stdio.h>
#include <stdlib.h>
int main()
{
//开辟内存
int* arr = (int*)calloc(5, sizeof(int));//分配空间,并初始化为0
if (arr == NULL)
{
perror("malloc");
return -1;
}
else
{
for (int i = 0; i < 5; i++)
{
printf("%d ", *(arr + i));
}
//释放内存
free(arr);
arr = NULL;
return 0;
}
}
calloc和malloc 的对比:
- 参数不一样
- 都是在堆区上申请内存空间,但是malloc不初始化,calloc会初始化为0。
如果要初始化,就使用calloc,不需要初始化,则使用malloc。
2.4 realloc
void* realloc(void* ptr, size_t size);
功能: 重新分配内存块——改变ptr所指向的内存块的大小。
- 重新分配成功,返回指向分配后内存块的指针,它可以是原本 ptr 指向的位置 ,也可以是一个新的位置
- 重新分配失败,在C99中,空指针表示函数分配存储失败,因此 ptr 所指向的内存块没有被修改。
- 如果size为0,则返回值取决于特定的库实现:它可能是空指针,也可能是不能解引用的其他位置。
-
如果传给realloc的是一个空指针,则realloc功能与malloc相同
新大小大于旧大小,且重新分配成功有两种情况:
情况1:原本的 ptr 所指向的内存块之后有足够大的空间,于是往后继续分配空间,总空间大
小为 size 字节,返回的地址不变
情况2:原本的 ptr 所指向的内存块之后没有足够大的空间,编译器找到一个新的足够大的空
间,将它作为新的内存块,还会将原来内存中的数据移动到 新 的空间,释放旧空间
(旧指针不为空),返回新空间的地址
int main()
{
//开辟内存
int* arr = (int*)calloc(5, sizeof(int));
if (arr == NULL)
{
perror("malloc");
}
else
{
for (int i = 0; i < 5; i++)
{
*(arr + i) = i;
printf("%d ", *(arr + i));
}
}
//重新分配内存
int* ptr = (int*)realloc(arr, sizeof(int) * 1000);
if (ptr != NULL)
{
if (ptr == arr)
{
printf("情况1\n");
}
else
{
printf("情况2\n");
}
arr = ptr;
for (int i = 5; i < 1000; i++)
{
*(arr + i) = i;
}
for (int i = 0; i < 1000; i++)
{
printf("%d ", *(arr + i));
if ((i+1) % 20 == 0)
{
printf("\n");
}
}
}
//释放内存
free(arr);
arr = NULL;
return 0;
}
3、常见的动态内存错误
3.1对NULL指针的解引用操作
对空指针解引用操作的危害:转载:【缺陷周话】第1期:空指针解引用_chongzhao2789的博客-CSDN博客
void test (){int * p = ( int * ) malloc ( INT_MAX / 4 );* p = 20 ; // 如果 p 的值是 NULL ,就会有问题free ( p );p = NULL;}可能出现的问题:结果未定义
3.2 对动态开辟空间的越界访问
void test (){int i = 0 ;int * p = ( int * ) malloc ( sizeof ( int ) * 10 );if ( NULL == p ){perror("malloc");}for ( i = 0 ; i <= 10 ; i ++ ){* ( p + i ) = i ; // 当 i 是 10 的时候越界访问,程序可能会崩溃}free ( p );p = NULL;}可能出现的问题:程序崩溃
3.3 对非动态开辟内存使用free释放
void test (){int a = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };int * p = & a ;free ( p ); //ok?}可能出现的问题: 结果未定义
3.4 使用free释放一块动态开辟内存的一部分
void test (){int * p = ( int * ) malloc ( 100 );if ( NULL == p ){perror("malloc");}for ( i = 0 ; i < 10 ; i ++ ){* p = i ;p ++ ;}free ( p ); //p 不再指向动态内存的起始位置}可能出现的问题:程序崩溃
3.5 对同一块动态内存多次释放
void test (){int * p = ( int * ) malloc ( 100 );if ( NULL == p ){perror("malloc");}free ( p );/*一系列操作*/free ( p ); // 重复释放}可能出现的问题:程序崩溃如果 free (p )之后直接将p赋为空指针,那么再次释放将不会出现问题
3.6 动态开辟内存忘记释放(内存泄漏)
void test (){int * p = ( int * ) malloc ( 100 );if ( NULL != p ){* p = 20 ;}}int main (){test ();while ( 1 );}malloc calloc realloc 所申请的空间,如果不想使用,需要free 释放如果不使用 free 释放 ,程序结束之后,会由操作系统回收。如果不使用 free 释放,程序也不结束,将发生内存泄露
总结:动态开辟的空间一定要释放,并且正确释放