At the end of last night's stream (Day 178), gasto5 asked, "what's wrong with pointers to inline functions?"
I suspect gasto5 might have misinterpreted something that Casey said a few minutes earlier in response to an earlier question by elxenoaizd ("Q: I noticed you prefer to keep things on the stack and return by value..."), which was: "Typically [...] if a struct is large and the function is not inlined, then yes, you want to use a pointer" [i.e., pass a pointer-to-struct-object to the function instead of having the function return a large struct].
But the answer to gasto5's actual question is: inherently, nothing (except that, when you use the pointer to call the function, the compiler might not do the inlining at that call site).
Consider:
| int f(int n) { return n + 42; }
|
If we ask the compiler for assembly code (/O1 /Ob1 /Fa with cl.exe; -S with GCC and Clang), the resulting .asm file will contain something like:
| ?f@@YAHH@Z PROC ; f, COMDAT
; File w:\handmade\code\x.cpp
; Line 2
lea eax, DWORD PTR [rcx+42]
ret 0
?f@@YAHH@Z ENDP ; f
|
Now, if we insert 'inline':
| inline int f(int n) { return n + 42;}
|
... then that definition of 'f' disappears from the assembly output. HOWEVER, if we then reference it from another function:
| inline int f(int n) { return n + 42;}
int g(int n) { return f(n); }
|
... then the assembly code contains two function definitions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | ; Function compile flags: /Ogspy
; COMDAT ?g@@YAHH@Z
_TEXT SEGMENT
n$ = 8
?g@@YAHH@Z PROC ; g, COMDAT
; File w:\handmade\code\x.cpp
; Line 3
lea eax, DWORD PTR [rcx+42]
ret 0
?g@@YAHH@Z ENDP ; g
_TEXT ENDS
; Function compile flags: /Ogspy
; COMDAT ?f@@YAHH@Z
_TEXT SEGMENT
n$ = 8
?f@@YAHH@Z PROC ; f, COMDAT
; File w:\handmade\code\x.cpp
; Line 2
lea eax, DWORD PTR [rcx+42]
ret 0
?f@@YAHH@Z ENDP ; f
_TEXT ENDS
|
One is the definition of g, which contains a body that looks just like the assembly code for f in the first version of the example (consistent with the command line option /Ob1, which asks for inline expansion).
But we *also* get an out-of-line definition of f (again, just like the first version of the example).
And in fact we can take its address (just as if it had been declared without 'inline'), which will refer to the out-of-line definition:
| inline int f(int n) { return n + 42;}
typedef int F(int);
F *h()
{
F *Result = f; // ok
return Result; // returns address of out-of-line definition of f
}
int g(int n) { return h()(n); } // ok; calls f(n)
|
So the effects of 'inline' are:
- it suggests to the compiler that we prefer the function to be inlined when it is called.
- it tells the linker to tolerate one extra copy of the out-of-line definition per .obj file.
Note, when the linker produces an EXE or DLL from multiple .obj files, it has to read and discard all but one of the many out-of-line definitions of a single inline function. So the compiler does duplicate work to generate them, and then the linker has to do work to read them before throwing them away.
So another benefit of a unity build is, we only generate one .obj file, so there are no duplicates in the first place.