第三章 函数与指针
程序中的栈和堆
- 程序帧是支持函数执行的内存区域,与堆共享内存区域
- 栈在程序帧下部,由下往上增长;堆在上部,由上往下增长
- 栈存放函数参数和局部变量,堆管理动态内存
- 栈帧保存以下元素
- 返回地址
- 局部数据存储
- 参数存储
- 栈指针和基指针
函数参数指针传递
- 函数参数传递时,实参传递给形参的是值传递,这会复制实参,不够高效,尤其是传递结构体时,我们不希望再一次复制完全的结构体。而使用指针传递,不但可以改变实参,还能更高效的传递参数。如传递结构体时,传递结构体指针即可,无需其他消耗。
- 如果传递时,使用指向常量的指针,则可以更高效的获取实参内容,而且不会改变实参内容。
传递指针的指针
对于以下代码
void allocateArray(int *arr, int size, int value)
{
arr=(int *)malloc(size * sizeof(int));
for(i=0;i<size;++i)
*(arr+i)=value;
}
int *vector = NULL;
allocateArray(&vector,10,1);
会产生问题
- 首先函数内部虽然将地址传给了
arr
,但是紧接着arr
的地址被重新通过malloc
赋值,所以并没有使用vector
的内容 - 函数释放后,分配的空间地址丢失,导致内存泄漏
为了解决以上的问题,我们使用指向指针的指针。
想改变的实参类型 | 形参形参的形式 |
---|---|
int | int * |
int * | int ** |
- 当函数改变的实参的值时,而不是形参创建的副本,那么函数传递就要传递实参的地址
- 当函数要改变的是实参本身就是指针,那么函数就要传递指针的地址,也就是函数的形参要定义成指向指针的指针。
所以修改代码
void allocateArray(int **arr, int size, int value)
{
*arr=(int *)malloc(size * sizeof(int));
for(i=0;i<size;++i)
*(*arr+i)=value;
}
int *vector = NULL;
allocateArray(&vector,10,1);
这样调用,函数内部分配的内存地址会直接传递给实参vector
,不会导致地址的丢失等问题
函数返回指针
- 声明
int * fcuntion()
即意味着此函数返回的是一个整型指针。注意与int (*fcuntion)()的区别
常用的函数返回指针技术
常用的返回指针技术有两种: - 函数内部使用
malloc
分配内存空间。调用者负责释放内存
int * allocateArray(int size, int value)
{
int *arr = malloc(size * sizeof(int));
for(int i=0;i<size;++i)
*(arr+i)=value;
return arr;
}
然后使用以下方式调用
int *vector = allocateArray(10,1);
for(i=0;i<10;++i)
printf("%p",*(vector+i));
最后要注意一定要释放内存,因为函数内部只负责了分配内存,要由调用者负责释放
free(vector);
- 函数内部只负责修改传递过来的指针并修改它,内存的分配和释放都有调用者负责。对于传递过来的指针,优先判断是否为空是个好习惯。如下例:
int * allocateArray(int *arr, int size, int value)
{ if(arr != NULL)
{
for(int i=0;i<size;++i)
*(arr+i)=value;
}
return arr;
}
然后使用以下方式调用
int* vector=(int *)malloc(size * sizeof(int));
allocateArray(vector,10,1);
书中说此方法不推荐,目前不知道为什么
以上两种方式,函数内部使用的内存都是处于堆区域,所以即使函数结束,弹出栈,堆区域的内容仍然存在,只要保留地址即可。但是函数返回指针不要返回函数内部定义的变量,这样函数结束弹出栈帧以后,变量立即消失,这样就出现问题
函数返回指针注意的问题
函数返回指针要注意四个事项:
- 返回未初始化的指针
- 返回指向无效地址的指针
- 返回局部变量指针
- 返回指针但是外部调用没有释放
在处理函数返回指针时,一定要根据内存方式注意以上四项。
完善的free函数
内置free()
函数存在以下问题:
- 不会检查传入指针是否为空
- 释放后不会将指针置为NULL 这时候可以创建自己的free函数
void safeFree(void **p){
if(p != NULL && *P != NULL){
free(*p);
*p =NULL;
}
}
其中void指针
意味着可以传入任何指针类型,定义**p
是因为释放的本身是个指针,需要使用传递指针的指针来真正操作指针。
更快的方式可以定义一个宏
#define safeFree(p) safeFree(void ** (&p))
配合safeFree
函数使用。
函数指针
- 函数指针定义的方式就是
int (*function)()
,这时候function
就作为一个指针指向了函数的地址。 - 函数指针对性能是有一定影响的,使用它处理器就无法配合流水分支预测
- 正常的函数声明中
int func()
,函数名func
没有明确定义是指针,还是其他类型。但是对打印函数名func
的地址,或者函数名取值后&func
的地址都是同一个值。所以我们只要知道正常的函数声明中,函数名可以等效为该函数的地址。 - 对于函数指针,建议使用
fptr
作为前缀 - 使用了函数指针,使用该指针调用函数,程序将不检查参数传递的是否正确
- 为函数指针声明一个类型定义会比较方便
typedef int (*fptrfunc)(int,int)
- 函数指针可以实现在函数调用中动态的调用其他函数,实现了函数作为参数的传递,这样可以使用C语言进行函数式编程。个人感觉函数指针是实现C++的某种基础。例如以下代码
typedef int (* fptrOperation)(int ,int);
int sum(int a, int b){ return a+b}
int sub(int a, int b){ return a-b}
int computer(fptrOperation operaton, int num1, int num 2) { return operaton(num1, num2) }
printf("%d\n",computer(sum,1,2));
printf("%d\n",computer(sub,5,3));
- 返回函数指针即用函数指针去声明一个函数,例
fptrOperation switchcode(char opecode)
- 当用函数指针去声明一个数组时,那么数组中的每个元素,都代表函数指针指向的一个函数操作,大大增加了函数调用的丰富性。例如
fptrOperation operaton[10]={NULL};