Re-exploration of Function Pointer
Questions raised
- What is the nature of function pointers?
- Is a function pointer as efficient as a direct call? Why?
- Can function pointers be combined with templates?
- Can function pointers work on static functions, member methods, static methods, pure virtual functions, lamda? What can I do if I can't?
- What other code besides function pointers can do similar things?
explore
What is the nature of function pointers?
This refers to the definition of function pointers recorded in Wikipedia
A function pointer, also called a subroutine pointer or procedure pointer is a pointer that point to a function,
A function is a block of code that performs a specific function and an executable interval. At the assembly language level, a calling function generally saves some current information, such as program counters and other information, onto the stack, then jumps to the entry address where the function executes, returns from the body of the function, and returns to the current main program.
In one sentence:
The essence of a function pointer is to point to the first address of an executable block of code, and a function is the first address of that executable block of code
A function can be called directly, while a function pointer needs to be dereferenced when it calls a function, but they both implement the same functions, one directly and the other indirectly.
Compare the two types of function calls at the assembly level, such as a function add, which is called in both ways mentioned earlier. Disassemble and view the underlying call format as follows
int add(int a, int b){ return a + b; } typedef decltype(add) *FP; int main(){ // Calling functions directly int c1 = add(1,2); // Calling a function through a function pointer FP p = add; int c2= p(1,2); return 0; }
The disassembly assembly code section is as follows, deleting the details and looking at only the parts called by the two functions. The disassembly code is as follows
..... # The first address of the add function 0000000140001530 <_Z3addii>: 140001530: 55 push %rbp 140001531: 48 89 e5 mov %rsp,%rbp 140001534: 89 4d 10 mov %ecx,0x10(%rbp) 140001537: 89 55 18 mov %edx,0x18(%rbp) 14000153a: 8b 55 10 mov 0x10(%rbp),%edx 14000153d: 8b 45 18 mov 0x18(%rbp),%eax 140001540: 01 d0 add %edx,%eax 140001542: 5d pop %rbp 140001543: c3 retq # First address of main function 0000000140001544 <main>: ...... 140001551: ba 02 00 00 00 mov $0x2,%edx 140001556: b9 01 00 00 00 mov $0x1,%ecx 14000155b: e8 d0 ff ff ff callq 140001530 <_Z3addii> 140001560: 89 45 fc mov %eax,-0x4(%rbp) 140001563: 48 8d 05 c6 ff ff ff lea -0x3a(%rip),%rax # 140001530 <_Z3addii> 14000156a: 48 89 45 f0 mov %rax,-0x10(%rbp) 14000156e: 48 8b 45 f0 mov -0x10(%rbp),%rax 140001572: ba 02 00 00 00 mov $0x2,%edx 140001577: b9 01 00 00 00 mov $0x1,%ecx 14000157c: ff d0 callq *%rax .....
From the analysis, you can see two assembly codes. The assembly code used by the function call is
callq 140001530
The assembly code for the function pointer calling the function is
lea -0x3a(%rip), %rax ;take add The first address of the function is put in rax register callq *%rax ;Indirect Call add,add Address stored in rax inside
As you can see, a function pointer call simply places the function's first address in a register and indirectly jumps to that location
How effective is a function pointer compared to a function call?
Find a description of the performance of function pointer calls from Wiki pedia as
Extensively using function pointers to call functions may produce a slow-down for the code on modern processors, because branch predictor may not be able to figure out where to branch to (it depends on the value of the function pointer at run time) although this effect can be overstated as it is often amply compensated for by significantly reduced non-indexed table lookups.
On modern processors, heavy use of function pointers to call functions can lead to slower code because branch predictors may not be able to determine where to branch (depending on the value of the runtime function pointer), although this effect may be exaggerated because it is often compensated for by a large reduction in non-index table lookups
Come from Function pointer - Wikipedia
This may mean that the performance differences between the two are not significant
Can functions be combined with templates?
Can be combined with templates
#include<stdio.h> template<typename type> type add(type type1, type type2){ return type1 + type2;} void func1(double (*p1)(double, double), double a, double b){ printf("The double value %f + %f = %f\n", a, b, p1(a,b)); } void func2(int (*p1)(int, int), int a, int b){ printf("The integer value %d + %d = %d\n", a, b, p1(a,b)); } int main(int argc, char *argv[]){ func1(add<double>, 1.0, 2.0); func2(add<int>, 1, 2); return 0; }
D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exe The double value 1.000000 + 2.000000 = 3.000000 The integer value 1 + 2 = 3
What defined function templates can a function pointer point to
Can function pointers work on static functions, member methods, static methods, pure virtual functions, lamda, or what can I do instead?
Acting on static functions
#include<stdio.h> // Static Function Definition static int add_func(int a, int b){ return a + b; } int main(int argc, char *argv[]){ typedef int (*calcu_ptr)(int, int); // Define function pointer type printf("The func pointer's size is %d\n", int(sizeof(calcu_ptr))); calcu_ptr c = &add_func; // Function pointer to add_func printf("The result of the %d + %d = %d", 1, 2, c(1,2)); return 0; }
D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exe The func pointer's size is 8 The result of the 1 + 2 = 3
Act on member methods
#include<stdio.h> class ALU{ // Complete some basic operations public: int alu_Add(int a, int b){ return a + b; } }; int main(int argc, char *argv[]){ typedef int (ALU::*calc_ptr)(int, int); // Define pointers to member methods calc_ptr c = &ALU::alu_Add; // Common member methods pointing to classes printf("The member func pointer's size is %d\n", int(sizeof (calc_ptr))); ALU *a = new ALU(); printf("The result of %d + %d = %d\n", 1, 2,(a->*c)(1,2)); // Use of the normal member method return 0; }
D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exe The member func pointer's size is 16 The result of 1 + 2 = 3
Point to Member Static Method
#include<stdio.h> class ALU{ // Complete some basic operations public: static int alu_Add(int a, int b){ return a + b; } }; int main(int argc, char *argv[]){ typedef int (*calc_ptr)(int, int); // Define Function Pointer calc_ptr c = &ALU::alu_Add; // Point to static member method printf("The static member func pointer's size is %d\n", int(sizeof (calc_ptr))); printf("The result of %d + %d = %d\n", 1, 2,c(1,2)); // Use static member method return 0; }
D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exe The static member func pointer's size is 8 The result of 1 + 2 = 3
Point to Pure Virtual Function
#include<stdio.h> class Operation{ public: virtual int Calcu(int a, int b) = 0; }; class Add:public Operation{ public: int Calcu(int a, int b) override{ return a + b; } }; class Sub:public Operation{ public: int Calcu(int a, int b) override{ return a - b; } }; int main(int argc, char *argv[]){ typedef int (Operation::*Calcu)(int, int); Calcu c = &Operation::Calcu; printf("The virtual member func's size is %d\n", int(sizeof(Calcu))); Add a; Sub s; printf("%d + %d = %d\n", 1, 2, (a.*c)(1,2)); printf("%d - %d = %d\n", 2, 1, (s.*c)(2,1)); return 0; }
D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exe The virtual member func's size is 16 1 + 2 = 3 2 - 1 = 1
Function pointer to lamda
#include<stdio.h> typedef int (*Calcu)(int, int); int main(){ Calcu c = [](int a, int b){return a + b;}; printf("The func pointer size is %d\n", int(sizeof (Calcu))); printf("%d + %d = %d\n", 1, 2, c(1,2)); return 0; }
D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exe The func pointer size is 8 1 + 2 = 3
From the code above, you can draw the following conclusions
- Function pointers can act on functions, member methods, static member methods, pure virtual functions, lamda
- When a function pointer points to a member method and a pure virtual function, it is twice as large as a normal function pointer, and its definition method is different, twice as large as a normal function pointer, because these methods are executed with a specific object, and the function pointer points not only to the address of the method, but also to the address of the owner (object) of the method. So it's two to three times larger than a normal function pointer
In addition to function pointers, what else can you do to achieve similar functionality
functor, a function is simply a statement block. It can implement some functions and return values. This is a function, but in the process of function call, the function executed by the function can only be handled by passing parameters and calling some global variables. The key is that it has no state, and the function object will not be like this. It defines a function object as not only a function but also a state of its own. If a piece of code can implement such a function, it is a function object. In C++, one way to implement a function object is to overload the () operator.
For example, an auto-cola vending machine, which only has 10 cans of cola, the user obtains the cola through the function buyCola. When 10 cans are bought, there will be no cola anymore, so you need to give a hint of selling out. In such a scenario, function alone cannot be achieved, but it can be achieved through the ** function object (or function functor)**, Because the latter can record this state, see the code
#include<stdio.h> class BuyCola{ public: BuyCola(int cola_count):cola_count(cola_count){} // overload operators void operator()(){ if(cola_count > 0){ printf("You will buy a cola\n"); cola_count--; }else{ printf("The cola is sold out!\n"); } } private: // Number of Cokes int cola_count; }; int main(){ BuyCola vending_maching(10); for (int i = 0; i < 13; ++i){ vending_maching(); } return 0; }
D:\ClionProject\C++Learn\cmake-build-debug\C__Learn.exe You will buy a cola You will buy a cola You will buy a cola You will buy a cola You will buy a cola You will buy a cola You will buy a cola You will buy a cola You will buy a cola You will buy a cola The cola is sold out! The cola is sold out! The cola is sold out!
For the introduction of specific functions, you can refer to Wiki or other information. The above is only personal understanding. If there are any errors, you are welcome to point out!
Here is the original Wiki intercept
A typical use of a function object is in writing callback functions. A callback in procedural languages, such as C, may be performed by using function pointers.[2] However it can be difficult or awkward to pass a state into or out of the callback function. This restriction also inhibits more dynamic behavior of the function. A function object solves those problems since the function is really a façade for a full object, carrying its own state.
Many modern (and some older) languages, e.g. C++, Eiffel, Groovy, Lisp, Smalltalk, Perl, PHP, Python, Ruby, Scala, and many others, support first-class function objects and may even make significant use of them.[3] Functional programming languages additionally support closures, i.e. first-class functions that can 'close over' variables in their surrounding environment at creation time. During compilation, a transformation known as lambda lifting converts the closures into function objects.