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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2795575/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 00:43:02  来源:igfitidea点击:

How does dereferencing of a function pointer happen?

c++cpointersfunction-pointers

提问by Lazer

Why and how does dereferencing a function pointer just "do nothing"?

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

This is what I am talking about:

这就是我要说的:

#include<stdio.h>

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

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


From a comment over here:

来自这里的评论:

function pointers dereference just fine, but the resulting function designator will be immediately converted back to a function pointer

函数指针解引用就好了,但是生成的函数指示符将立即转换回函数指针



And from an answer here:

这里的答案:

Dereferencing (in way you think) a function's pointer means: accessing a CODE memory as it would be a DATA memory.

Function pointer isn't suppose to be dereferenced in that way. Instead, it is called.

I would use a name "dereference" side by side with "call". It's OK.

Anyway: C is designed in such a way that both function name identifier as well as variable holding function's pointer mean the same: address to CODE memory. And it allows to jump to that memory by using call () syntax either on an identifier or variable.

取消引用(以您认为的方式)函数的指针意味着:访问 CODE 内存,就像访问 DATA 内存一样。

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

我会与“调用”并排使用名称“取消引用”。没关系。

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



How exactlydoes dereferencing of a function pointer work?

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

采纳答案by Norman Ramsey

It's not quite the right question. For C, at least, the right question is

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

What happens to a function value in an rvalue context?

右值上下文中的函数值会发生什么变化?

(An rvalue context is anywhere a name or other reference appears where it should be used as a value, rather than a location—basically anywhere except on the left-hand side of an assignment. The name itself comes from the right-hand side of an assignment.)

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

OK, so what happens to a function value in an rvalue context? It is immediately and implicitly converted to a pointer to the original function value. If you dereference that pointer with *, you get the same function value back again, which is immediately and implicitly converted into a pointer. And you can do this as many times as you like.

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

Two similar experiments you can try:

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

  • What happens if you dereference a function pointer in an lvaluecontext—the left-hand side of an assignment. (The answer will be about what you expect, if you keep in mind that functions are immutable.)

  • An array value is also converted to a pointer in an lvalue context, but it is converted to a pointer to the elementtype, not to a pointer to the array. Dereferencing it will therefore give you an element, not an array, and the madness you show doesn't occur.

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

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

Hope this helps.

希望这可以帮助。

P.S. As to whya function value is implicitly converted to a pointer, the answer is that for those of use who use function pointers, it's a great convenience not to have to use &'s everywhere. There's a dual convenience as well: a function pointer in call position is automatically converted to a function value, so you don't have to write *to call through a function pointer.

PS 至于为什么将函数值隐式转换为指针,答案是,对于那些使用函数指针的人来说,不必&到处使用's是非常方便的。还有一个双重方便:调用位置的函数指针会自动转换为函数值,因此您不必*通过函数指针编写调用。

P.P.S. Unlike C functions, C++ functions can be overloaded, and I'm not qualified to comment on how the semantics works in C++.

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

回答by Potatoswatter

C++03 §4.3/1:

C++03 §4.3/1:

An lvalue of function type T can be converted to an rvalue of type “pointer to T.” The result is a pointer to the function.

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

If you attempt an invalid operation on a function reference, such as the unary *operator, the first thing the language tries is a standard conversion. It's just like converting an intwhen adding it to a float. Using *on a function reference causes the language to take its pointer instead, which in your example, is square 1.

如果您尝试对函数引用进行无效操作,例如一元运算*符,则语言尝试的第一件事就是标准转换。就像将 anint添加到float. 使用*上的功能引用导致的语言把它的指针代替,这在你的榜样,是广场1。

Another case where this applies is when assigning a function pointer.

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

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

Note that this doesn'twork the other way.

请注意,这不适用于其他方式。

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

Function reference variables are nice because they (in theory, I've never tested) hint to the compiler that an indirect branch may be unnecessary, if initialized in an enclosing scope.

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

In C99, dereferencing a function pointer yields a function designator. §6.3.2.1/4:

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

A function designator is an expression that has function type. Except when it is the operand of the sizeof operator or the unary & operator, a function designator with type ‘‘function returning type'' is converted to an expression that has type ‘‘pointer to function returning type''.

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

This is more like Norman's answer, but notably C99 has no concept of rvalues.

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

回答by Hans Passant

Put yourself in the shoes of the compiler writer. A function pointer has a well defined meaning, it is a pointer to a blob of bytes that represent machine code.

让自己站在编译器作者的角度。函数指针具有明确定义的含义,它是指向表示机器代码的字节 blob 的指针。

What do you do when the programmer dereferences a function pointer? Do you take the first (or 8) bytes of the machine code and reinterpret that as a pointer? Odds are about 2 billion to one that this won't work. Do you declare UB? Plenty of that going around already. Or do you just ignore the attempt? You know the answer.

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

回答by MSalters

How exactly does dereferencing of a function pointer work?

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

Two steps. The first step is at compile time, the second at runtime.

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

In step one, the compiler sees it has a pointer and a context in which that pointer is dereferenced (such as (*pFoo)()) so it generates code for that situation, code that will be used in step 2.

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

In step 2, at runtime the code is executed. The pointer contains some bytes indicating which function should be executed next. These bytes are somehow loaded into the CPU. A common case is a CPU with an explicit CALL [register]instruction. On such systems, a function pointer can be simply the address of a function in memory, and the derefencing code does nothing more than loading that address into a register followed by a CALL [register]instruction.

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

回答by eigenslacker

It happens with a few implicit conversions. Indeed, per the C standard:

它发生在一些隐式转换中。事实上,按照 C 标准:

ISO/IEC 2011, section 6.3.2.1 Lvalues, arrays, and function designators, paragraph 4

A function designatoris an expression that has function type. Except when it is the operand of the sizeofoperator or the unary &operator, a function designator with type “function returning type” is converted to an expression that has type “pointer to function returning type”.

ISO/IEC 2011,第 6.3.2.1 节 Lvalues、数组和函数指示符,第 4 段

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

Consider the following code:

考虑以下代码:

void func(void);

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

Here, the function designator funchas the type “function returning void” but is immediately converted to an expression that has type “pointer to function returning void”. However, if you write

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

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

then the function designator funchas the type “function returning void” but the unary &operator explicitly take the address of that function, eventually yielding the type “pointer to function returning void”.

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

This is mentioned in the C standard:

C标准中提到了这一点:

ISO/IEC 2011, section 6.5.3.2 Address and indirection operators, paragraph 3

The unary &operator yields the address of its operand. If the operand has type “type”, the result has type “pointer to type”.

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

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

In particular, dereferencing a function pointer is redundant. Per the C standard:

特别是,取消引用函数指针是多余的。根据 C 标准:

ISO/IEC 2011, section 6.5.2.2 Function calls, paragraph 1

The expression that denotes the called function shall have type “pointer to function returning void” or returning a complete object type other than an array type. Most often, this is the result of converting an identifier that is a function designator.

ISO/IEC 2011, section 6.5.3.2 Address and indirection operators, paragraph 4

The unary *operator denotes indirection. If the operand points to a function, the result is a function designator.

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

表示被调用函数的表达式的类型应为“指向函数返回的指针void”或返回数组类型以外的完整对象类型。大多数情况下,这是转换作为函数指示符的标识符的结果。

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

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

So when you write

所以当你写

ptr();

the function call is evaluated with no implicit conversion because ptris alreadya pointer to function. If you explicitly dereference it with

函数调用的计算没有隐式转换,因为ptr已经是一个指向函数的指针。如果您明确取消引用它

(*ptr)();

then the dereferencing yields the type “function returning void” which is immediately converted back to the type “pointer to function returning void” and the function call occurs. When writing an expression composed of xunary *indirection operators such as

然后取消引用产生类型“函数返回void”,该类型立即转换回“指向函数返回的指针void”类型,并发生函数调用。编写由x一元*间接运算符组成的表达式时,例如

(****ptr)();

then you just repeat the implicit conversions xtimes.

那么你只需重复隐式转换x次。



It does make sense that calling functions involves function pointers. Before executing a function, a program pushes all of the parameters for the function onto the stack in the reverse order that they are documented. Then the program issues a callinstruction indicating which function it wishes to start. The callinstruction does two things:

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

  1. First it pushes the address of the next instruction, which is the return address, onto the stack.
  2. Then, it modifies the instruction pointer %eipto point to the start of the function.
  1. 首先它将下一条指令的地址,即返回地址,压入堆栈。
  2. 然后,它修改指令指针%eip以指向函数的开始。

Since calling a function does involve modifying an instruction pointer, which is a memory address, it makes sense that the compiler implicitly converts a function designator to a pointer to function.

由于调用函数确实涉及修改指令指针,这是一个内存地址,编译器将函数指示符隐式转换为函数指针是有道理的。



Even though it may seems unrigorous to have these implicit conversions, it can be useful in C (unlike C++ which have namespaces) to take advantage of the namespace defined by a struct identifier to encapsulate variables.

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

Consider the following code:

考虑以下代码:

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;
}

It is possible to hide the implementation of the library in other translation units and to choose to only expose the struct encapsulating the pointers to functions, to use them in place of the actualfunction designators.

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