Christope's answer is correct. This is to supplement.
A function pointer cannot itself provide asynchronous behavior. The standards actually bar this. I'm far more familiar with the C standard than the C++ standard, so I'll use that. My understanding is that both should be approximately the same on this point.
What the C11 standard says about functions and function pointers
Let's start with the definition of a function call in C, given in 6.5.2.2 paragraph 3:
A postfix expression followed by parentheses () containing a possibly empty, comma- separated list of expressions is a function call. The postfix expression denotes the called function. The list of expressions specifies the arguments to the function.
And modified by the constraint in paragraph 1:
The expression that denotes the called function (92) shall have type pointer to function returning void or returning a complete object type other than an array type.
Importantly, the accompanying footnote 92 says:
Most often, this is the result of converting an identifier that is a function designator.
So, the C11 standard basically defines a function call as something that calls a function pointer. And, for this purpose, named function identifiers are automatically converted into function pointers to the code in the identifier. Thus, C sees no difference between functions and function pointers.
An experiment
While it's always good to refer to the standard, it's also pretty useful to just look at how solid implementations do things. Let's do a test where we write fairly simple code and then look at the underlying assembly
The code:
#include <stdio.h>
#include <stdlib.h>
typedef void (*my_func_ptr)(int,int);
void my_function(int x, int y)
{
printf("x = %d, y = %d, x + y = %d\n",x,y,x+y);
}
int main()
{
/* declared volatile so the compiler has to call the function through
* the pointer and cannot optimize it to call the function directly */
volatile my_func_ptr fp = my_function;
my_function(3,5);
fp(3,6);
return 0;
}
I compiled the code using gcc
on Mac OS X with default optimizations (gcc -o fptr fptr.c
), which is actually a gcc frontend to the LLVM library. To look at the assembly, I ran the program under lldb
, set a breakpoint at main
, and issued the disassemble -f
commmand, which disassembles the current function. I use settings set target.x86-disassembly-flavor intel
for Intel-style assembly. The default in lldb
is AT&T style, which looks a bit different.
The main
routine in assembly is this:
push rbp
mov rbp, rsp
sub rsp, 0x20 ; sets up the stack frame
mov edi, 0x3 ; my_function(3,5). 1st arg: edi
mov esi, 0x5 ; 2nd arg: esi
lea rax, qword ptr [rip - 0x59] ; loads address of my_function into rax
mov dword ptr [rbp - 0x4], 0x0
mov qword ptr [rbp - 0x10], rax ; saves address of my_function on stack
call 0x100000ed0 ; explicit call to my_function
mov eax, 0x0
mov edi, 0x3 ; fp(3,6). 1st arg: edi
mov esi, 0x6 ; 2nd arg: esi
mov rcx, qword ptr [rbp - 0x10] ; rcx <- address of my_function from stack
mov dword ptr [rbp - 0x14], eax
call rcx ; call address at rcx
mov eax, dword ptr [rbp - 0x14]
add rsp, 0x20
pop rbp
ret
Notice that both function calls are essentially the same. They use the same assembly. Both times the actual call is invoked with a call
op. The only difference is that the first time the address is hard coded and the second time, the address is stored in the rcx
register. Also notice that there's nothing asynchronous about the code.
What C11 says about sequence points
When you start reasoning about sequence points, you actually find that, within a single thread, the standard disallows the kind of asynchronous behavior that you expected. In most cases, C11 constrains the compiler to execute code that is separated by a sequence point in sequential order. In Section 5.1.2.3 (program execution), the execution order of the program is defined as a series of sequence points. The relevant definition is essentially in paragraph 3:
Sequenced before is an asymmetric, transitive, pair-wise relation between evaluations executed by a single thread, which induces a partial order among those evaluations. Given any two evaluations A and B, if A is sequenced before B, then the execution of A shall precede the execution of B.
And later in that paragraph:
The presence of a sequence point between the evaluation of expressions A and B implies that every value computation and side effect associated with A is sequenced before every value computation and side effect associated with B.
Basically, this establishes that code separated by a sequence point must be executed synchronously (in order). However, the standard provides an out if the compiler can reason that two pieces of code cannot affect each other, in paragraph 4:
In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).
So, how do function pointers enter this? Appendix C clarifies what that a sequence point lies between expression statements, which are essentially statements that end with a semicolon (see 6.8.3). This includes function calls.
How this bars asynchronous execution of function pointers
Consider two sequential function calls:
f();
g();
Neither takes an argument, so the reasoning is a bit simpler. The calls must be executed in order unless the compiler can reason that any side effect of f()
is unused in g()
and vice versa. The only way compilers can reason about this in a function is if the function's code is available to the compiler. In general, this is not possible for function pointers, because the pointer could point to any function that satisfies the constraints of the function pointer type.
Note that in some cases the compiler can infer the correct function (if the function pointer is only assigned once and exists in local scope), but this is often not the case. Thus, the compiler must execute the functions in the order presented, and the first function must return before the second.
What about threading and coroutine libraries
The C11 standard has different rules for threading. Notice that Section 5.1.2.3 restricts itself to execution within a single thread. Coroutine libraries that play with the stack essentially break the C11 machine model, and are bound to a particular set of implementations (ie: not necessary portable to any C environment). A coroutine library essentially has to supply its own set of sequential-ordering guarantees.