How does dereferencing of a function pointer happen?

前端 未结 5 775
忘掉有多难
忘掉有多难 2020-11-22 07:58

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

This is what I am talking about:

#include

void hello() { print         


        
5条回答
  •  太阳男子
    2020-11-22 08:43

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

    ISO/IEC 2011, section 6.3.2.1 Lvalues, arrays, and function designators, paragraph 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”.

    Consider the following code:

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

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

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

    then the function designator func has 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”.

    This is mentioned in the C standard:

    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”.

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

    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.

    So when you write

    ptr();
    

    the function call is evaluated with no implicit conversion because ptr is already a pointer to function. If you explicitly dereference it with

    (*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 x unary * indirection operators such as

    (****ptr)();
    

    then you just repeat the implicit conversions x times.


    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 call instruction indicating which function it wishes to start. The call instruction does two things:

    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 %eip to point to the start of the function.

    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.

    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 actual function designators.

提交回复
热议问题