C 中函数指针的正确值类型

Correct value type for function pointers in C

我试图理解 C 中的函数指针。在互联网上阅读它时(主要是堆栈溢出 QA)- 我遇到了两种可以为函数指针赋值的方法

#include <stdio.h>

double add(int a, double b) {
  return a + b;
}

double subtract(int a, double b) {
  return a - b;
}

int main() {
  int op_a = 23;
  double op_b = 2.9;

  // Case 1. Just pass the function (not its address)
  double (*math_op)(int a, double b) = add;
  printf("The output of Math Operation %f\n", math_op(op_a, op_b));

  // Case 2. Pass the function's address
  math_op = &subtract;
  printf("The output of Math Operation %f\n", math_op(op_a, op_b));

  printf("End of program\n");
  return 0;
}

我有几个问题

  1. 在上面的代码中 - 将值赋值给函数指针的正确方法是什么。我看到一些关于堆栈溢出的答案遵循 案例 1 中的约定,我还看到一些答案遵循 案例 2 中的约定。两者似乎都适合我。哪一个是正确的(或更可取的)?
  2. 此外,为了调用函数指针,我看到了 2 种调用它们的方法 - math_op(op_a, op_b)(*math_op)(op_a, op_b)。同样,是否有更好的方法来执行此操作 - 两者似乎都适合我。

我觉得

Function calls (§6.5.2.2/1):

The expression that denotes the called function shall have type pointer to function returning void or returning an object type other than an array type.

足以回答您的两个问题。函数的名称被隐式转换为 指向函数类型的指针,因此在将函数的地址分配给函数指针时使用 address-of 运算符是多余的。至于通过函数指针调用函数,语法与 "ordinary" 函数调用没有什么不同。

这确实是 C 语言的一个怪癖。* 和(单个)& 在获取函数地址时会被忽略。

int main() {
    printf("%p %p %p", (void*)add, (void*)************add, (void*)&add);
}

当"dereferencing"一个函数指针

时,*被忽略
int main() { 
    double (*math_op)(int a, double b) = add;
     printf("%p %p %f", (void*)math_op, (void*)*************math_op, (***************math_op)(1, 1.0));
}

没关系。但是要保持一致。大多数情况下,我没有看到在这种情况下使用 *&

情况 1 是首选。

&是可选的,可以取一个函数的地址(就像:char buf[100]; char *bp = buf;

首选情况 1 的原因是 语法 相同 用于从函数 [=78] 赋值给函数指针=]地址或另一个函数指针:

typedef double (*math_func)(int a, double b);
math_func math_op = add;
math_func math_op2 = sub;
math_func math_op3 = math_op2;

类似地,这就是为什么你可以说:math_op(2,23) 作为 shorthand 的原因:(*math_op)(2,23)


更新:

Pointer typedefs are not preferred (by me anyway)

它们肯定有助于函数指针 [Jonathan Leffler 例外]。

如果你有(例如)10 个函数需要接受一个函数指针的参数,你是否想在所有 10 个函数原型中拼写出来,特别是如果函数指针本身有 8 个参数?

假设[在某些 API] 你有一个函数指针:

typedef void (*funcptr)(int x,int y,int z);

考虑您具有以下形式的函数:

void func(funcptr f,...);

并且,您有一个这些函数的调用链:

a -> b -> c -> d -> apiabc

函数 a-d 调用 f——只有 apiabc 做。只有 apiabc 知道如何正确调用 f。其他人只是传递 opaque [给他们] 指针,以便 apiabc 可以对其进行操作。

如果 funcptr 必须改变,改变 a-d 就会有问题(即它们 不是 API 的一部分--只是它的用户)。

一般来说,在指针 typedefs 上,IMO,它们很好 [=7​​8=]if 它们相当明显并且 一致

这将是一个用法示例:

typedef struct node *Node;              // poor
typedef struct node *pNode;             // MS (common but, IMO, poor)
typedef struct node *nodeP;             // okay
typedef struct node *nodeptr;           // better?
typedef const struct node *nodecptr;    // better?

IMO,MS 版本很差,因为如果我们对所有名称进行排序,pNode 会与所有 [无关] pWhatever 混在一起,并且不会在从左向右看时倾向于 node nodeP 占主导地位的部分在前(即与节点相关)。

IMO,POSIX 声称 whatever_t 作为其类型名称的省是 hubris。但是,发生冲突的可能性很小 [无论如何,对我来说,因为我总是使用可能 不会 冲突的名称。如果基于未来POSIX类型的碰撞,我输了,必须改变我的定义。

因此,我个人的惯例是:

typedef struct node node_t;
typedef node_t *node_p;
typedef const node_t *node_pc;

只要始终如一地应用,任何此类约定都可以使用。在一组给定的代码中,如果第一次遇到 foo_p,是的,必须在 .h 文件中查找定义。之后,当看到bar_p时,约定就已经知道了。

对我来说,这通常不是问题,因为当我遇到不熟悉的代码库时,我总是先查看 .h 文件中的定义 before查看 .c

中的代码