如何解除引用函数指针?

时间:2010-05-08 20:50:47

标签: c++ c pointers function-pointers

为什么以及如何解除引用函数指针只是“什么都不做”?

这就是我所说的:

#include<stdio.h>

void hello() { printf("hello"); }

int main(void) { 
    (*****hello)(); 
}

来自对here的评论:

  

函数指针解除引用   很好,但由此产生的功能   指定者将立即   转换回函数指针


从答案here

  

解除引用(以你认为的方式)a   函数的指针意味着:访问一个   CODE内存,因为它将是一个DATA   存储器中。

     

不假设函数指针   以这种方式解除引用。相反,它   被称为。

     

我会使用名称“dereference”方面   与“呼叫”并排。没关系。

     

无论如何:C是以这种方式设计的   这两个函数名称标识符为   以及变量保持功能   指针的含义相同:地址为CODE   记忆。它允许跳转到那个   内存也可以使用call()语法   关于标识符或变量。


完全如何取消引用函数指针?

5 个答案:

答案 0 :(得分:49)

这不是一个正确的问题。对于C,至少,正确的问题是

  

rvalue上下文中的函数值会发生什么?

(rvalue上下文是名称或其他引用出现在应该用作值的位置,而不是位置 - 基本上除了在赋值的左侧之外的任何位置。名称本身来自右侧 - 作业的一面。)

好的,那么rvalue上下文中的函数值会发生什么?它立即并隐式转换为指向原始函数值的指针。如果您使用*取消引用该指针,则会再次获得相同的函数值,该函数值会立即并隐式转换为指针。你可以随心所欲地多次这样做。

您可以尝试两种类似的实验:

  • 如果取消引用左值上下文中的函数指针(分配的左侧)会发生什么。 (答案将是关于你期望的,如果你记住功能是不可变的。)

  • 数组值也会转换为左值上下文中的指针,但它会转换为指向元素类型的指针,而不是指向数组的指针。因此,取消引用它将为您提供一个元素,而不是一个数组,并且您显示的疯狂不会发生。

希望这有帮助。

P.S。至于为什么函数值被隐式转换为指针,答案是对于那些使用函数指针的人来说,不必在任何地方使用&都是非常方便的。 。还有一个双重的便利:调用位置的函数指针会自动转换为函数值,因此您不必编写*来通过函数指针调用。

P.P.S。与C函数不同,C ++函数可以重载,我没有资格评论语义在C ++中是如何工作的。

答案 1 :(得分:7)

C ++03§4.3/ 1:

  

函数类型T的左值可以转换为“指向T的指针”的右值。结果是指向函数的指针。

如果对函数引用(例如一元*运算符)尝试无效操作,则语言尝试的第一件事是标准转换。这就像将int添加到float时转换一样。在函数引用上使用*会导致语言取而代之,在您的示例中,它是方形1。

另一种适用的情况是分配函数指针。

void f() {
    void (*recurse)() = f; // "f" is a reference; implicitly convert to ptr.
    recurse(); // call operator is defined for pointers
}

请注意,此不会以其他方式工作。

void f() {
    void (&recurse)() = &f; // "&f" is a pointer; ERROR can't convert to ref.
    recurse(); // OK - call operator is *separately* defined for references
}

函数引用变量很好,因为它们(理论上,我从未测试过)提示编译器,如果在封闭范围内初始化,则间接分支可能是不必要的。

在C99中,取消引用函数指针会产生函数指示符。 §6.3.2.1/ 4:

  

函数指示符是具有函数类型的表达式。除非它是sizeof运算符或一元&amp;运算符的操作数。运算符,类型为''函数返回类型''的函数指示符将转换为类型为''指向函数返回类型''的表达式。

这更像诺曼的答案,但值得注意的是C99没有右手的概念。

答案 2 :(得分:2)

把自己放在编译器编写者的脚下。函数指针具有明确定义的含义,它是指向代表机器代码的字节blob的指针。

当程序员取消引用函数指针时你会怎么做?您是否获取机器代码的第一个(或8个)字节并将其重新解释为指针?赔率约为20亿比1,这是行不通的。你宣布UB吗?很多事情已经发生了。或者你只是忽略了这种尝试?你知道答案。

答案 3 :(得分:1)

函数指针的解引用究竟是如何工作的?

两个步骤。第一步是在编译时,第二步是在运行时。

在第一步中,编译器看到它有一个指针和一个上下文,其中该指针被解除引用(例如(*pFoo)()),因此它为该情况生成代码,代码将在步骤2中使用。 / p>

在步骤2中,在运行时执行代码。指针包含一些字节,指示接下来应该执行哪个函数。这些字节以某种方式加载到CPU中。常见的情况是具有明确CALL [register]指令的CPU。在这样的系统上,函数指针可以只是内存中函数的地址,而derefencing代码只是将该地址加载到寄存器后跟CALL [register]指令。

答案 4 :(得分:0)

它发生了一些隐式转换。实际上,按照C标准:

ISO / IEC 2011,第6.3.2.1节Lvalues,数组和函数标识符,第4段

功能指示符是具有功能类型的表达式。除非它是sizeof运算符或一元&运算符的操作数,否则类型为“ function returning type ”的函数指示符将转换为类型为“指向返回 type ”的函数的指针。

考虑以下代码:

void func(void);

int main(void)
{
    void (*ptr)(void) = func;
    return 0;
}

这里,函数指示符func的类型为“返回 void 的函数”,但立即转换为类型为“指向返回的函数的指针”的表达式。 void ”。但是,如果您写

void (*ptr)(void) = &func;

然后函数指示符func的类型为“函数返回 void ”,但一元&运算符显式获取该函数的地址,最终产生类型为“指向返回 void 的函数的指针”。

这是C标准中提到的:

ISO / IEC 2011,第6.5.3.2节地址和间接操作符,第3段

一元&运算符产生其操作数的地址。如果操作数的类型为“ type ”,则结果的类型为“指向 type 的指针”。

尤其是,取消引用函数指针是多余的。按照C标准:

ISO / IEC 2011,第6.5.2.2节,函数调用,第1段

表示被调用函数的表达式的类型必须为“指向返回 void 的函数的指针”或返回除数组类型以外的完整对象类型。通常,这是转换标识符的结果,该标识符是一个功能指示符。

ISO / IEC 2011,第6.5.3.2节,地址和间接操作符,第4段

一元*运算符表示间接。如果操作数指向一个函数,则结果为一个函数指示符。

所以当你写

ptr();

对函数调用的评估没有任何隐式转换,因为ptr已经已经指向函数了。如果您使用

明确取消引用它
(*ptr)();

然后解引用将产生类型“函数返回 void ”,该类型立即转换回类型“指向函数返回 void 的指针”,然后发生函数调用。编写由 x 一元*一元间接操作符(例如

)组成的表达式时
(****ptr)();

然后您只需重复隐式转换 x 次。


有意义的是,调用函数涉及函数指针。在执行功能之前,程序会按照记录的相反顺序将功能的所有参数压入堆栈。然后程序发出一条call指令,指示它希望启动哪个功能。 call指令有两件事:

  1. 首先,它将下一条指令的地址(即返回地址)压入堆栈。
  2. 然后,它修改指令指针%eip指向函数的开始。

由于调用函数确实涉及到修改指令指针(该指令是一个内存地址),因此编译器会隐式地将函数指定符转换为指向函数的指针。


尽管进行这些隐式转换似乎并不严格,但它在C语言中还是很有用的(与具有名称空间的C ++不同) 利用结构标识符定义的名称空间来封装变量。

考虑以下代码:

void create_person(void);
void update_person(void);
void delete_person(void);

struct Person {
    void (*create)(void);
    void (*update)(void);
    void (*delete)(void);
};

static struct Person person = {
    .create = &create_person,
    .update = &update_person,
    .delete = &delete_person,
};

int main(void)
{
    person.create();
    person.update();
    person.delete();
    return 0;
}

可以将库的实现隐藏在其他翻译单元中,并选择仅公开封装了指向函数指针的结构,以使用它们代替 actual 函数指示符。 / p>