深入理解C指针 指针和函数

局部变量也称为自动变量,他总是分配在栈帧上。

3.1 程序的堆和栈

3.1.1 程序栈

  程序栈是支持函数执行的内存区域,通常和堆共享。也就是说,程序栈和堆共享同一块内存区域。程序栈通常占据这块区域的下部,而堆用的则是上部。

程序栈存放栈帧(stack frame),栈帧有时也称为活跃记录或活跃帧。栈帧存放函数参数和局部变量。堆管理动态内存。

调用函数时,函数的栈帧被推到栈上,栈向上“长出”一个栈帧。当函数终止时,其栈帧从程序栈上弹出。栈帧所使用的内存不会被清理,但最终可能被推到程序栈上的另一个栈帧覆盖。

动态分配的内存来自堆,对向下“生长”。随着内存的分配和释放,堆中会布满碎片。尽管堆是向下生长的,但这只是个大体方向,实际上内存可能在堆上的任意位置分配。

3.1.2 栈帧的组织

  栈帧由以下几种元素组成:

  • 返回地址:函数完成后要返回的程序内部地址
  • 局部数据存储:为局部变量分配的内存
  • 参数存储:为函数参数分配的内存
  • 栈指针和基指针:运行时系统来管理栈的指针

栈指针通常指向栈顶部。基指针(帧指针)通常存在并指向栈帧内部的地址。这两个指针都不是C指针。它们是运行时系统管理程序栈的地址。

将栈帧推到程序栈上时,系统可能会耗尽内存,这种情况称为栈溢出。要牢记每个线程通常都会有自己的程序栈。

3.2 通过指针传递和返回数据

  传递参数(包括指针)时,传递的是他们的值。也就是说,传递给函数的是参数的一个副本。当涉及大型数据结构时,传递参数的指针会高效。

3.2.1 用指针传递数据

  void swap(int *pnum1, int *pnum2)
  {
      int tmp;
      tmp = *pnum1;
      *pnum1 = *pnum2;
      *pnum2 = tmp;
  }

3.2.2 用值传递数据

  如果不通过指针传递参数,那么交换就不会发生。num1和num2中保存的只是参数的副本。修改形参不会影响实参。

  void swap(int num1, int num2)
  {
      int tmp;
      tmp = num1;
      num1 = num2;
      num2 = tmp;
  }

3.2.3 传递指向常量的指针

如果只是传递指针,数据就能被修改,如果不希望数据被修改,就要传递指向常量的指针。

  void pass(const int * num1,int num2)  不能修改通过指向常量的指针传进来的值

3.2.4 返回指针

函数返回指针时可能存在几个潜在的问题:

  • 返回为初始化的指针
  • 返回指向无效地址的指针
  • 返回局部变量的指针
  • 返回指针但是没有释放内存

3.2.5 局部数据指针

  返回局部变量的指针,一旦函数执行完栈帧就被弹出了,尽管数据可能还在,但如果之后继续执行其他函数,该内存就会被覆盖。

如果把变量声明为static,这样把函数的作用域限制在函数内部,但是分配在栈帧外面,避免其他函数复写变量值。但每次调用该函数都会重复利用这个变量,这样相当于每次都把上一次调用的结果覆盖掉了。此外,静态数组必须声明为固定长度,这样会限制函数处理变长数组的能力。

3.2.7 传递指针的指针

  void allocateArray(int **arr, int size, int value) {
      *arr = (int *)malloc(size * sizeof(int));//解引整数指针的指针得到的是整数指针
      if (*arr != NULL){
          for (int i = 0; i < size; i++)
              *(*arr + i) = value;
        }
    }

这个函数可用下面代码测试:

  int *vector = NULL;
    allocateArray(&vector, 5, 6);

实现自己的free函数:

void saferFree(void **pp){
if(pp != NULL && *pp != NULL){
free(*pp);
*pp = NULL;
}
}

使用指针的指针允许修改传入的指针,而使用void类型则可以传入所有类型的指针。如果调用这个函数时没有显示的把指针类型转换为void会产生警告,执行显示转换就不会有警告。下面这个safeFree宏调用saferFree函数,执行类型转换,并使用了取地址操作符,这样就省去函数使用者做类型转换和传递指针的地址:

 #define safeFree(p) safeerFree( (void**) &p )

3.3 函数指针

  函数指针是持有函数地址的指针。

3.3.1 声明函数指针

  类型标识符 (* 指针变量名)( )

  void (* foo)( )     void:返回类型  foo:函数指针变量名  参数

使用函数指针时一定要小心,因为C不会检查参数传递是否正确。以下是声明函数指针的一些例子:

  int (*f1)(double); //传入 double,返回 int
  void (*f2)(char*); //传入 char 指针,没有返回值
  double* (*f3)(int,int); //传递两个整数,返回 double 指针

不要把返回指针值的函数和函数指针搞混淆。

  int *f4(); //f4是一个函数,返回一个整数指针
  int (*f5)(); //f5是一个返回整数的函数指针
  int* (*f6)(); //f6是一个返回整数指针的函数指针

3.3.2 使用函数指针

  int (*fptr)(int);
  int square(int num){
      return num*num;
  }
  ...
  int n = 5;
  fptr = square; // fptr = &square;     让指针等于函数的入口地址。
  printf("%d squard is %d ",n,fptr(n));

  在这种上下文环境中,编译器会忽略取地址符操作,所以你可以写出来,但没必要这么做。

因为fptr是一个函数指针,那么*fptr就是该指针所指向的函数,所以(*fptr)( )就是调用该函数的方式,ANSIC标准允许简写fptr( )。

为函数指针声明一个类型定义会比较方便,类型定义的名字是声明的最后一个元素。

  typedef int (*funcptr)(int);
  ...
  funcptr fptr1;
  fptr1 = square;
  printf("%d squard is %d ",n,fptr1(n));

3.3.3 传递函数指针

 传递函数指针很简单,只要把函数指针声明作为函数参数即可。

int add(int num1, int num2) {
        return num1 + num2;
    }
    int sub(int num1, int num2) {
        return num1 - num2;
    }
    typedef int(*fptr)(int, int);

    int compute(fptr operation, int num1, int num2) {
        return operation(num1, num2);
    }

    printf("%d
", compute(add, 5, 6));
    printf("%d
", compute(sub, 5, 6));

3.3.4 返回函数指针

  返回函数指针需要把函数的返回类型声明为函数指针。

int add(int num1, int num2) {
    return num1 + num2;
}
int sub(int num1, int num2) {
    return num1 - num2;
}
typedef int(*fptr)(int, int);

fptr select(char opcode) {
    switch (opcode){
    case '+':return add; 
    case '-':return sub; 
    }
    return 0;
}
int evaluate(char opcode, int num1, int num2) {
    fptr operation = select(opcode); //函数指针赋值
    return operation(num1, num2);
}
    printf("%d
", evaluate('+', 5, 6));
    printf("%d
", evaluate('-', 5, 6));

 

3.3.5 使用函数指针数组

  类型标识符( *指针变量名[  ] ) ( );

    int (*operations[128])(int, int)={ NULL };  这个数组的所有元素都被初始化为NULL

也可以用typedef来声明这个数组:

   typedef int (*operation)(int, int); 如果数组的初始化值是一个语句块,系统会将块中的值赋给连续的数组元素。

   operation operations[128]={ NULL };

#include<stdio.h>
#include<stdlib.h>

int plus(int a, int b){
    return(a + b);
}
int minus(int a, int b){
    return(a - b);
}
int multiplied(int a, int b){
    return(a * b);
}
int divided(int a, int b){
    return(a / b);
}
int percent(int a, int b){
    return(a % b);
}
void main1()
{
    //int(*p)(int a, int b); 函数指针
    int(*pn[5])(int a, int b) = { plus,minus, &multiplied ,&divided,percent }; //函数指针数组初始化
    int ax = 100; int by = 20;

    for (int i = 0; i < 5; i++) //下标方式循环数组
        printf("%d
", pn[i](ax, by)); //分别调用五种运算输出结果

    system("pause");
}
void main()        //函数指针数组名是二级函数指针
{
    int(*pn[5])(int a, int b) = { plus,minus, multiplied ,divided,percent };
    int ax = 100; int by = 20;
    //通过二级函数指针的方式遍历函数指针数组
    for (int(**p)(int a, int b) = pn; p < pn + 5; p++) //指针循环
    {
        printf("%d
", (*p)(ax, by));
    }

    system("pause");
}

3.3.6 比较函数指针

  可以用相等和不等操作符来比较函数指针。

3.3.7 转换函数指针

  我们可以将指向某个函数的指针转换为其他类型的指针,不过要谨慎使用,因为运行时系统不会验证函数指针所用的参数是否正确。也可以再转回来,得到的结果和原指针相同,但函数指针的长度不一定相等。

int add(int num1, int num2) {
    return num1 + num2;
}

typedef int(*fptrToSing)(int);
typedef int(*fptrToInts)(int, int);

void main()
{
    fptrToInts fptrFirst = add;
    fptrToSing fptrSecond = (fptrToSing)fptrFirst;
    fptrFirst = (fptrToInts)fptrSecond;
    printf("%d
", fptrFirst(5, 6));

无法保证函数指针和数据指针转换后正常工作。

  void* 指针不一定能用在函数指针上。也就是说不应该像下面这样把函数指针赋给void * 指针:  void * pv = add;

 不过在交换函数指针时,通常会见到如下声明所示的“基本”函数指针类型。这里把fptrBase声明为指向不接受参数也不返回结果的函数的函数指针。

  typedef void (*fptrBase)( ); 

原文地址:https://www.cnblogs.com/Yang-Xin-Yi/p/13532364.html