第三章 函数与指针

程序中的栈和堆

  • 程序帧是支持函数执行的内存区域,与堆共享内存区域
  • 栈在程序帧下部,由下往上增长;堆在上部,由上往下增长
  • 栈存放函数参数和局部变量,堆管理动态内存
  • 栈帧保存以下元素
    • 返回地址
    • 局部数据存储
    • 参数存储
    • 栈指针和基指针

函数参数指针传递

  • 函数参数传递时,实参传递给形参的是值传递,这会复制实参,不够高效,尤其是传递结构体时,我们不希望再一次复制完全的结构体。而使用指针传递,不但可以改变实参,还能更高效的传递参数。如传递结构体时,传递结构体指针即可,无需其他消耗。
  • 如果传递时,使用指向常量的指针,则可以更高效的获取实参内容,而且不会改变实参内容。

    传递指针的指针

    对于以下代码
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);

会产生问题

  1. 首先函数内部虽然将地址传给了arr,但是紧接着arr的地址被重新通过malloc赋值,所以并没有使用vector的内容
  2. 函数释放后,分配的空间地址丢失,导致内存泄漏

为了解决以上的问题,我们使用指向指针的指针。

想改变的实参类型 形参形参的形式
int int *
int * int **
  1. 当函数改变的实参的值时,而不是形参创建的副本,那么函数传递就要传递实参的地址
  2. 当函数要改变的是实参本身就是指针,那么函数就要传递指针的地址,也就是函数的形参要定义成指向指针的指针。

所以修改代码

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);

书中说此方法不推荐,目前不知道为什么

以上两种方式,函数内部使用的内存都是处于堆区域,所以即使函数结束,弹出栈,堆区域的内容仍然存在,只要保留地址即可。但是函数返回指针不要返回函数内部定义的变量,这样函数结束弹出栈帧以后,变量立即消失,这样就出现问题

函数返回指针注意的问题

函数返回指针要注意四个事项:

  1. 返回未初始化的指针
  2. 返回指向无效地址的指针
  3. 返回局部变量指针
  4. 返回指针但是外部调用没有释放

在处理函数返回指针时,一定要根据内存方式注意以上四项。

完善的free函数

内置free()函数存在以下问题:

  1. 不会检查传入指针是否为空
  2. 释放后不会将指针置为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};

results matching ""

    No results matching ""