为什么要做以下工作?

void foo() {
    cout << "Foo to you too!\n";
};

int main() {
    void (*p1_foo)() = foo;
    void (*p2_foo)() = *foo;
    void (*p3_foo)() = &foo;
    void (*p4_foo)() = *&foo;
    void (*p5_foo)() = &*foo;
    void (*p6_foo)() = **foo;
    void (*p7_foo)() = **********************foo;

    (*p1_foo)();
    (*p2_foo)();
    (*p3_foo)();
    (*p4_foo)();
    (*p5_foo)();
    (*p6_foo)();
    (*p7_foo)();
}

当前回答

这里有几个部分允许所有这些运算符的组合以相同的方式工作。

所有这些工作的根本原因是函数(如foo)可以隐式转换为指向该函数的指针。这就是为什么void (*p1_foo)() = foo;Works: foo被隐式转换为一个指向自身的指针,该指针被赋值给p1_foo。

一元&当应用于一个函数时,产生一个指向该函数的指针,就像它应用于一个对象时产生一个对象的地址一样。对于指向普通函数的指针,由于隐式函数到函数指针的转换,它总是冗余的。在任何情况下,这就是void (*p3_foo)() = &foo;的工作原理。

一元*当应用于函数指针时,产生指向函数,就像它应用于指向对象的普通指针时产生指向对象一样。

这些规则可以组合在一起。考虑倒数第二个例子,**foo:

首先,foo被隐式转换为指向自身的指针,第一个*被应用于该函数指针,再次产生函数foo。 然后,结果再次隐式转换为指向自身的指针,并应用第二个*,同样产生函数foo。 然后它再次隐式转换为函数指针并赋值给变量。

你可以添加多少*,结果总是一样的。*越多越快乐。

我们也可以考虑你的第五个例子&*foo:

首先,foo被隐式转换为一个指向自身的指针;应用一元*,再次产生foo。 然后,&被应用于foo,产生一个指向foo的指针,foo被赋值给变量。

不过,&只能应用于函数,而不能应用于已转换为函数指针的函数(当然,除非函数指针是变量,在这种情况下,结果是指针到指针到函数的指针;例如,你可以在列表中添加void (**pp_foo)() = &p7_foo;)。

这就是为什么&&foo不起作用:&foo不是一个函数;它是一个右值函数指针。但是,&*&*&*&*&*& &*foo可以工作,&******&foo也可以,因为在这两个表达式中&总是应用于函数而不是右值函数指针。

还要注意,您不需要使用一元*来通过函数指针进行调用;(* p1_foo) ();和(p1_foo) ();同样的结果,还是因为函数到函数指针的转换。

其他回答

当从指针调用foo时,甚至圆括号和星号都可以省略,就像直接用原名称调用函数一样,即(*p1_foo)()等价于p1_foo()。

如果你仍然不太相信@JamesMcNellis的答案,这里有一个证明。这是Clang编译器的AST(抽象语法树)。抽象语法树是编译器内部程序结构的内部表示形式。

void func1() {};
void test() {
    func1();
    (*func1)();
    (&func1)();

    void(*func1ptr)(void) = func1;
    func1ptr();
    (*func1ptr)();
    //(&func1ptr)();//error since func1ptr is a variable, &func1ptr is its address which is not callable.
}

AST:

//func1();
|-CallExpr //call the pointer
| `-ImplicitCastExpr //implicitly convert func1 to pointer
|   `-DeclRefExpr //reference func1

//(*func1)();
|-CallExpr //call the pointer
| `-ImplicitCastExpr //implicitly convert the funtion to pointer
|   `-ParenExpr //parentheses
|     `-UnaryOperator //* operator get function from the pointer
|       `-ImplicitCastExpr //implicitly convert func1 to pointer
|         `-DeclRefExpr //reference func1

//(&func1)();
|-CallExpr //call the pointer
| `-ParenExpr //parentheses
|   `-UnaryOperator //& get pointer from func1
|     `-DeclRefExpr //reference func1

//void(*func1ptr)(void) = func1;
|-DeclStmt //define variable func1ptr
| `-VarDecl //define variable func1ptr
|   `-ImplicitCastExpr //implicitly convert func1 to pointer
|     `-DeclRefExpr  //reference func1

//func1ptr();
|-CallExpr  //call the pointer
| `-ImplicitCastExpr //implicitly convert func1ptr to pointer
|   `-DeclRefExpr //reference the variable func1ptr

//(*func1ptr)();
`-CallExpr //call the pointer 
  `-ImplicitCastExpr //implicitly convert the function to pointer
    `-ParenExpr //parentheses
      `-UnaryOperator //* get the function from the pointer
        `-ImplicitCastExpr //implicitly convert func1ptr to pointer
          `-DeclRefExpr //reference the variable func1ptr

&和*是在C语言中声明为函数的符号上的幂等运算,这意味着func == *func == &func == *&func,因此*func == **func,但它们有不同的类型,所以你会得到警告。

传递给函数的函数地址的参数类型可以是int()或int(*)(),并且可以传递为*func, func或&func。调用(&func)()与func()或(*func)()相同。Godbolt链接。

* and & have no meaning on a function symbol, and instead of producing an error, the compiler chooses to interpret it as the address of func in both cases. The function does not exist as a separate pointer, like an array symbol, therefore &arr is the same as arr, because it is not a physical pointer with an address at runtime, it's a logical pointer at compiler level. Furthermore *func would read the first byte of the function code, which is an a code section, and rather than produce a compiler error or allow it to be a runtime error segmentation fault, it's just interpreted by the compiler as the address of the function.

声明为函数指针的符号上的&将获得指针的地址(因为它现在是一个实际的指针变量,显示在堆栈或数据部分),而funcp和*funcp仍将被解释为函数的地址。

我认为记住C语言只是底层机器的一个抽象也是有帮助的,而这正是这个抽象泄漏的地方之一。

从计算机的角度来看,函数只是一个内存地址,如果执行它,就执行其他指令。因此,C语言中的函数本身被建模为一个地址,这可能会导致函数与它所指向的地址“相同”的设计。

这里有几个部分允许所有这些运算符的组合以相同的方式工作。

所有这些工作的根本原因是函数(如foo)可以隐式转换为指向该函数的指针。这就是为什么void (*p1_foo)() = foo;Works: foo被隐式转换为一个指向自身的指针,该指针被赋值给p1_foo。

一元&当应用于一个函数时,产生一个指向该函数的指针,就像它应用于一个对象时产生一个对象的地址一样。对于指向普通函数的指针,由于隐式函数到函数指针的转换,它总是冗余的。在任何情况下,这就是void (*p3_foo)() = &foo;的工作原理。

一元*当应用于函数指针时,产生指向函数,就像它应用于指向对象的普通指针时产生指向对象一样。

这些规则可以组合在一起。考虑倒数第二个例子,**foo:

首先,foo被隐式转换为指向自身的指针,第一个*被应用于该函数指针,再次产生函数foo。 然后,结果再次隐式转换为指向自身的指针,并应用第二个*,同样产生函数foo。 然后它再次隐式转换为函数指针并赋值给变量。

你可以添加多少*,结果总是一样的。*越多越快乐。

我们也可以考虑你的第五个例子&*foo:

首先,foo被隐式转换为一个指向自身的指针;应用一元*,再次产生foo。 然后,&被应用于foo,产生一个指向foo的指针,foo被赋值给变量。

不过,&只能应用于函数,而不能应用于已转换为函数指针的函数(当然,除非函数指针是变量,在这种情况下,结果是指针到指针到函数的指针;例如,你可以在列表中添加void (**pp_foo)() = &p7_foo;)。

这就是为什么&&foo不起作用:&foo不是一个函数;它是一个右值函数指针。但是,&*&*&*&*&*& &*foo可以工作,&******&foo也可以,因为在这两个表达式中&总是应用于函数而不是右值函数指针。

还要注意,您不需要使用一元*来通过函数指针进行调用;(* p1_foo) ();和(p1_foo) ();同样的结果,还是因为函数到函数指针的转换。