博客主页:https://blog.csdn.net/weixin_46094737?type=blog
欢迎评论⭐留言 如有错误敬请指正!
本文由小学生廉原创,首发于 CSDN🙉🙉🙉
未来很长,值得我们全力奔赴更美好的生活!💞💞💞
一、内存泄露
使用malloc()、calloc()、realloc()动态分配的内存,如果没有指针指向他,就无法进行任何操作,这段内存会一直被程序占用,知道程序运行结束由操作系统回收。
请看下面代码:
#include <stdio.h>
#include <stdlib.h>
int main(){
char *p = (char*)malloc(100 * sizeof(char));
p = (char*)malloc(50 * sizeof(char));
free(p);
p = NULL;
return 0;
}
1、该程序中,第一次分配100字节的内存,并将p指向他;第二次分配50字节的内存,依然使用p指向他。
这就导致了一个问题,第一次分配的100字节的内存没有指针指向他了,而且我们也不知道这块内存的地址,所以就再也无法找回了,也没法释放了,这块内存就成了垃圾内存,虽然毫无用处,但依然占用资源,唯一的办法就是等程序运行结束后由操作系统回收。
这就是内存泄露,可以理解为程序和内存失去了联系,再也无法对他进行任何的操作。
内存泄露形象的比喻是“操作系统可提供给所有程序使用的内存空间正在被某个程序榨干”,最终结果是程序运行时间越长,占用内存空间越来越多,最终用尽全部内存空间,整个系统崩溃。
2、再来看一种内存泄露情况:
int *pOld = (int*) malloc( sizeof(int) );
int *pNew = (int*) malloc( sizeof(int) );
这两句代码分别创建了一块内存,并且将内存的地址传给了指针pOld和pNew。此时指针pOld和pNew分别指向这两块内存。
如果接下来进行这样的操作:
pOld = pNew;
pOld指针就指向了pNew指向的内存地址,这时候再进行释放内存的操作:
free(pOld);
此时释放的pOld所指向的内存空间就是原来pNew指向的,于是这块空间被释放掉了。但是pOld原来指向的那块内存空间还没有被释放,不过因为没有指针指向这块内存,所以这块内存就造成了丢失。
3、另外,你不应该进行类似下面这样的操作:
malloc( 100 * sizeof(int) );
这样的操作没有意义,因为没有指针指向分配的内存,无法使用,而且无法通过free()释放掉,造成了内存泄露。
总结
free()函数的用处在于实时的回收内存,如果程序简单,程序结束前也不会使用过多的内存,不会降低系统性能,那么也可以不写free()函数。当程序结束后,操作系统释放内存。
但是如果在开发大型程序时不写free()函数,后果是非常严重的。这是因为很可能在程序中要重复一万次分配10M的内存,如果每次进行分配后都使用free()函数释放用完的内存空间,那么整个程序只需要使用10MB内存就可以运行。但是如果不使用free()函数,那么程序就要使用100GB的内存!这其中包括绝大部分的虚拟内存,而由于虚拟内存的操作需要读写磁盘,因此,这样会极大的影响到系统的性能,系统因此可能崩溃。
因此,在程序中使用malloc()分配内存时都对应的写出一个free()函数是一个良好的编程习惯。这不但能体现在处理大型程序时的必要性,并能在一定程度上体现程序优美的风格和健壮性。
二、函数指针数组
顾名思义,就是每个元素都是函数指针的数组,直接在函数指针名后面加上数组符号[ ]即可。
声明形式:type (*func[ ])(参数列表 )
C语言函数不可以定义为数组,只能通过函数指针来操作定义函数指针数组。
#include <stdio.h>
int add(int x,int y)//普通函数
{
return x+y;
}
int sub(int x,int y)//普通函数
{
return x-y;
}
int cheng(int x,int y)//普通函数
{
return x*y;
}
int chu(int x,int y)//普通函数
{
return x/y;
}
int (*fun[4])(int,int)={add,sub,cheng,chu};//函数指针数组,将函数功能模块封装起来,由一个函数指针数组(*fun[n])统一管理
int main ()
{
int x,y;
char ch;
printf("请输入运算符:\n");
ch=getchar();
switch(ch)
{
case '+':
printf("请输入两个整数(以空格分隔,回车结束):\n");
scanf("%d %d",&x,&y);
printf("%d+%d=%d",x,y,fun[0](x,y));
break;
case '-':
printf("请输入两个整数(以空格分隔,回车结束):\n");
scanf("%d %d",&x,&y);
printf("%d-%d=%d",x,y,fun[1](x,y));
break;
case '*':
printf("请输入两个整数(以空格分隔,回车结束):\n");
scanf("%d %d",&x,&y);
printf("%d*%d=%d",x,y,fun[2](x,y));
break;
case '/':
printf("请输入两个整数(以空格分隔,回车结束):\n");
scanf("%d %d",&x,&y);
printf("%d/%d=%d",x,y,fun[3](x,y));
break;
default:
break;
}
// fun = &add;
// printf("10加上5等于%d\n",fun(10,5));
// fun = ⊂
// printf("10减去5等于%d\n",fun(10,5));
// fun = &cheng;
// printf("10乘以5等于%d\n",fun(10,5));
// fun = &chu;
// printf("10除5等于%d",fun(10,5));
return 0;
}
运行结果:
使用条件限制一:
在子函数中,需要限制两个整型形参变量。
使用条件限制二:
子函数的返回值需要是一个整型变量。
数组指针和指针数组
1.数组指针:定义 int (*p)[n];
由于()的优先级高,首先说明p是一个int类型指针,它是指向一个整型(int)的一维数组,这个一维数组的长度是n,也可以说是总共有n个格子。数组指针也称指向一维数组的指针,亦称行指针。
数组指针也可以称为“数组的指针”,首先这个变量是一个指针,其次,”数组”修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址。
2.指针数组:定义 int *p[n];
指针数组可以说成是”指针的数组”,由于*p没有括号,首先这个变量是一个数组,其次,”指针p”修饰这个数组,意思是说这个数组的所有元素都是指针类型,而指针所占的字节数和其类型无关,只与系统有关,在32位系统下,任何类型的指针占据4个字节,在64位系统下,任何类型的指针占据8个字节。
示例
使用一个二维数组指针,遍历打印一个二维数组
#include <stdio.h>
int main()
{
int i,j;
int arr[2][2]={{20,30},{40,50,}};
int (*p)[2];//定义一个二维数组指针
p=arr;//指针指向数组首地址
for(i=0;i<2;i++)
{
for(j=0;j<2;j++)
printf("arr[%d][%d]=%d ",i,j,*(*(p+i)+j));//遍历打印数组所有元素
printf("\n");
}
return 0;
}
运行结果:
使用一个指针数组,打印一串字符
#include <stdio.h>
int main()
{
int a=100;
printf("a的地址为:%#p\n",&a);
char *name[2]={"hello","nihao"};//指针的数组,数组名字name
int i;
for(i=0;i<2;i++)
printf("%s ",name[i]);
printf("\n");
int *arr01[1]={&a};
printf("指针数组首地址为:%#p\n",*arr01);
printf("指针数组首地址元素的值为:%d",*arr01[0]);
return 0;
}
运行结果: