动态内存管理

一、相关函数原型

原则-谁申请谁释放

这类函数返回值都是 void * 可以与其他指针直接赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SYNOPSIS
#include <stdlib.h> // 此类函数的头文件

// 动态分配一个内存为 size 大小的空间---指针函数---返回一个地址(开辟的内存的地址)
void *malloc(size_t size);

// 释放
void free(void *ptr);

// 动态分配 内存为 nmenb * sizeof(size) 大小的空间,可用于数组(连续的内存空间)
void *calloc(size_t nmemb, size_t size);

// 重新将指针ptr指向的地址 分配内存为 size 大小的空间 --- ptr必须是malloc或者calloc调用后返回的某一个指针
// 当原来使用malloc或者calloc开辟的内存空间不够时,若原来的地址后续内存足够,则在原来的地址的基础上进行扩充
// 若原来的地址后续内存不够,则会将原来malloc或者calloc开辟的内存空间进行释放重新找一块内存空间
void *realloc(void *ptr, size_t size);

// 重新分配连续的空间
void *reallocarray(void *ptr, size_t nmemb, size_t size);




二、函数使用与注意事项

1.malloc()

实例程序–malloc()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *p = NULL;
p = (int *)malloc(sizeof(int)); // 强制转换(int *)可以省略,malloc()返回值为void *

if(p==NULL)
{
printf("malloc error");
exit(1); // 程序异常时,提前终止程序运行
}

*p = 10;
printf("%p --> %d\n",p,*p);
// 释放内存
free(p);

exit(0);
}

实例程序(分配连续的空间内存,用于数组)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *p;
int num = 5;

// 创建连续的空间内存
p = malloc(sizeof(int) * num);
int i;
for(i = 0;i<5;i++)
{
scanf("%d",p+i);
}
for(i = 0;i < 5;i++)
{
printf("%d ",p[i]);
}
printf("\n");
exit(0);
}


2.内存泄漏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>

void func(int *p,int n)
{
p = malloc(n);
if(p == NULL)
exit(1);
return;
}

int main()
{
int num = 100;
int *p = NULL;

func(p,num);
free(p); // 此时释放p相当于释放一个空指针
return 0;
}

该程序存在内存泄漏,func()函数中的形式参数int *p 为局部变量,当main()中调用该函数结束后,p的指针就会被释放,但是malloc(n)是动态开辟的内存,只能进行主动释放,此时p指针已经被释放,不会指向此动态开辟的内存,因此这块动态开辟内存就会丢失

修改程序

方法1:二级指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>

void func(int **p,int n)
{
*p = malloc(n);
if(*p == NULL)
exit(1);
return;
}

int main()
{
int num = 100;
int *p = NULL;

func(&p,num); // 函数传入的是 main()函数中定义的int *p的地址,func函数调用结束也不会对其存在影响,因此*p仍然直线malloc(n)开辟的内存
free(p);
return 0;
}

方法2: 返回指针—-指针函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>
#if 0
void func(int **p,int n)
{
*p = malloc(n);
if(*p == NULL)
exit(1);
return;
}
#endif

void *func(int *p,int n)
{
p = malloc(n);
if(p == NULL )
exit(1);
return p;
}

int main()
{
int num = 100;
int *p = NULL;

func(p,num);
free(p);
return 0;
}


3.野指针的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *p = NULL;
p = malloc(sizeof(int));
if(p==NULL)
{
printf("malloc() error! \n");
exit(1);
}

*p = 10;
printf("%p---->%d\n",p,*p);

free(p);

*p = 123;
printf("%p---->%d\n",p,*p);

exit(0);
}

运行结果:

image-20230308220924668

在上述的运行结果,虽然free(p)之后的结果,对*p进行赋值,输出*p的值以及指针变量p里面存储的地址值(发现不变,仍然指向之前动态开辟的内存空间),但是实际此时的p已经是一个野指针(或者悬空指针),可能由于编译器的问题里面对应的地址值并没有变化,若程序在释放p之后在该地址出存储了一个特别重要的变量,这样操作将会造成很危险的结果,因此需要在释放的之后在该指针有下一个合法的指向之前对指针p赋值为NULL

悬空指针:是指向了一个已经被释放或无效的内存地址的指针。这种情况经常发生在一些编程错误中,例如在C或C++等低级编程语言中,当对象被删除或内存被释放后,任何仍然引用该内存地址的指针都可能变成悬空指针

修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *p = NULL;
p = malloc(sizeof(int));
if(p==NULL)
{
printf("malloc() error! \n");
exit(1);
}

*p = 10;
printf("%p---->%d\n",*p);

free(p);
p = NULL; // 在指针P有下一个合法的指向之前指向为NULL

*p = 123;
printf("%p---->%d\n",*p);

exit(0);
}

运行结果:

image-20230308221752696

上述结果出现段错误,因为free()实际并没有将该段地址中的值清除掉,而是变量p对于开辟的内存空间再也没有引用的权限,此时的指针p以及为NULL,无法对其在进行赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

int main()
{
int *p = NULL;
p = malloc(sizeof(int));
if(p==NULL)
{
printf("malloc() error!\n");
exit(1);
}

*p = 10;
printf("%p---%d\n",p,*p);
free(p);

p = NULL;
printf("%p\n",p);
exit(0);
}

运行结果:

image-20231017213749506



4. 传入的指针参数,作为临时(局部)变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

// 取出数组中第 i 个元素 , 使用指针p 指向arr[i-1]
bool GetElem(int arr[],int i,int *p)
{
int length = sizeof(arr) / sizeof(arr[0]); // 得到传入数组的长度
if(length==0 || i<1 || i>length)
return false;
// 使得指针p 指向arr[i-1]
p = &arr[i-1];
return true;
}

int main()
{
int list[5] = {1,2,3,4,5};
int i = 2;
int *p;
GetElem(list,i,p);
printf("%d\n",*p);
exit(0);
}

编译结果

原因: C语言中如果一个函数接受一个数组作为参数,那么数组将会被退化为指针,正确的用法不在其他函数内部使用sizeof()函数

而是在main()函数中将数组的长度计算出来得到`length,在进一步将length作为参数传入值函数中

运行结果:

image-20230308224626590

最后得到的结果,p指针指向的是一个不确定的区块,可以将p仍然看作为野指针

原因:在调用函数GetElem()之后,传入的指针作为局部变量,函数调用完,指针p中就被释放,又被指向不确定区块

改正1,将指针变量作为返回值返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

int* GetElem(int arr[],int i,int *p)
{
int length = sizeof(arr) / sizeof(arr[0]);
if(length==0 || i<1 || i>length)
exit(1);
p = &arr[i-1];
return p;
}

int main()
{
int list[5] = {1,2,3,4,5};
int i = 2;
int *p;
p = GetElem(list,i,p);
printf("%d\n",*p);
exit(0);
}

// 最后得到期望的值

改正2,二级指针操作函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

bool GetElem(int arr[],int i,int **p)
{
int length = sizeof(arr) / sizeof(arr[0]);
if(length==0 || i<1 || i>length)
exit(1);
*p = &arr[i-1];
return true;
}

int main()
{
int list[5] = {1,2,3,4,5};
int i = 2;
int *p;
GetElem(list,i,&p);
printf("%d\n",*p);
exit(0);
}

// 最后得到期望的值

例程2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <stdlib.h>

void swap(int *p,int *q,int* c,int* d)
{
p = c;
q = d;
}

int main()
{
int a = 3;
int b = 4;
int c=5;
int d=6;
int *p = &a;
int *q = &b;

printf("*p:%d----*q:%d\n",*p,*q);
swap(p,q,&c,&d);
printf("*p:%d----*q:%d\n",*p,*q);
exit(0);
}

在函数swap()中使得指针p,q重新指向c,d,此操作无效,因为传入的指针,作为局部变量,在函数中对指针进行重新指向的操作,在函数调用结束之后,指针将指向原来的值

image-20230308225342832