发生一些隐式转换。实际上,按照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
指令执行两件事:
- 首先,它将下一条指令的地址(即返回地址)压入堆栈。
- 然后,它修改指令指针
%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;
}
可以将库的实现隐藏在其他翻译单元中,并选择只公开封装了指向函数的指针的结构,以使用它们代替实际的函数指示符。