函数指针的解除引用如何发生?


73

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

这就是我在说的:

#include<stdio.h>

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

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

这里的评论:

函数指针取消引用就可以了,但是生成的函数指示符将立即转换回函数指针


这里的答案:

取消引用(按照您的想法)函数的指针意味着:访问CODE内存,因为它就像是DATA内存。

不能以这种方式取消对函数指针的引用。相反,它被称为。

我将使用“取消引用”和“调用”的名称。没关系。

无论如何:C的设计方式使得函数名称标识符以及变量保存函数的指针都具有相同的指向CODE存储器的地址。并且它允许通过在标识符或变量上使用call()语法跳转到该内存。


函数指针的取消引用到底如何工作?

Answers:


58

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

在右值上下文中,函数值会怎样?

(右边的值上下文是在任何地方的名称或其它引用出现在它应该被用作一个值,而不是一个位置的任何位置基本上除了在赋值的左手侧。名字本身来自右侧的侧-手一个作业。)

好的,那么右值上下文中的函数值会怎样?立即将其隐式转换为指向原始函数值的指针。如果使用取消引用该指针*,则会再次返回相同的函数值,该函数值将立即隐式转换为指针。您可以根据需要执行多次操作。

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

  • 如果在左值上下文(赋值的左侧)中取消引用函数指针会发生什么情况。(如果记住函数是不可变的,那么答案将是关于您的期望的。)

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

希望这可以帮助。

PS关于为什么将函数值隐式转换为指针,答案是对于那些使用函数指针的用户,不必在&各处使用,这是一个极大的便利。还有双重便利:位于调用位置的函数指针会自动转换为函数值,因此您不必编写*通过函数指针即可进行调用。

PPS与C函数不同,C ++函数可以重载,因此我无权评论语义在C ++中的工作方式。


1
您能否详细说明“ ...隐式转换为指向原始函数值的指针”?您指的是函数的返回值吗?如果是这样,是否意味着编译器会自动将该返回值保留为左值,尽管函数的返回值是右值。谢谢!
tutormike '16

在调用位置一个函数指针会自动转换为函数值-但事实恰恰相反。函数调用和数组索引运算符都要求“ function” /“ array”操作数实际上需要是一个指针
Antti Haapala

8

C ++ 03§4.3/ 1:

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

如果您尝试对函数引用(例如一元运算*符)执行无效操作,则该语言首先尝试的是标准转换。就像将anint添加到中时将其转换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运算符或一元&运算符的操作数,否则将类型为“函数返回类型”的函数指定符转换为具有类型为“函数返回指针的指针”的表达式。

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


在函数参考上”实际上,表达式不能具有引用类型。表达式可以是右值或左值。
curiousguy 2011年

1

让自己陷入编译器编写者的视线中。函数指针具有明确定义的含义,它是指向表示机器代码的字节的blob的指针。

当程序员取消引用函数指针时,该怎么办?您是否采用了机器代码的前(或8)个字节并将其重新解释为指针?这是不可能的,赔率约为20亿。你申报UB吗?已经有很多这样的事情了。还是您只是忽略尝试?你知道答案。


7
如果我是编译器作者,那么我将其视为非法。这是一个有点误导的答案。
rpjohnst,2011年

1

函数指针的取消引用到底如何工作?

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

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

在步骤2中,在运行时执行代码。该指针包含一些字节,指示接下来应执行哪个功能。这些字节以某种方式加载到CPU中。常见的情况是带有显式CALL [register]指令的CPU 。在这样的系统上,函数指针可以只是存储器中函数的地址,而去引用代码只不过是将该地址装入CALL [register]指令后的寄存器中。


1

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

ISO / IEC 2011,第6.3.2.1节,左值,数组和函数代号,第4段

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

考虑以下代码:

void func(void);

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

此处,函数指示符func的类型为“ function returning void”,但立即转换为类型为“ pointer to function returning void”的表达式。但是,如果您写

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

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

C标准中提到了这一点:

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

一元运算&符产生其操作数的地址。如果操作数的类型为“ type ”,则结果的类型为“ pointer to 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;
}

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.