为什么以及如何解除引用函数指针只是“什么都不做”?
这就是我所说的:
#include<stdio.h>
void hello() { printf("hello"); }
int main(void) {
(*****hello)();
}
来自对here的评论:
函数指针解除引用 很好,但由此产生的功能 指定者将立即 转换回函数指针
从答案here:
解除引用(以你认为的方式)a 函数的指针意味着:访问一个 CODE内存,因为它将是一个DATA 存储器中。
不假设函数指针 以这种方式解除引用。相反,它 被称为。
我会使用名称“dereference”方面 与“呼叫”并排。没关系。
无论如何:C是以这种方式设计的 这两个函数名称标识符为 以及变量保持功能 指针的含义相同:地址为CODE 记忆。它允许跳转到那个 内存也可以使用call()语法 关于标识符或变量。
完全如何取消引用函数指针?
答案 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
指令有两件事:
%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>