Introduction:
I have been creating a simple wrapper classes. I randomly found out that (or it appears to be) an inline function still compiled into a function call. I created an example class to test things out and this is what I found:
Consider the following class:
//compile with MSVC
class InlineTestClass
{
public:
int InternalInt;
int GetInt() {return InternalInt;}
inline int GetInt_Inl() {return InternalInt;}
//__forceinline -Forces the compiler to implement the function as inline
__forceinline int GetInt_ForceInl() {return InternalInt;}
};
This class has 3 functions for reference.
- The GetInt function is a standard function.
- The GetInt_Inl function is an inline function.
- The GetInt_ForceInl function is an ensured inline function in case of the compiler deciding not to implement GetInt_Inl as inline function
Implemented like so:
InlineTestClass itc;
itc.InternalInt = 3;
int myInt;
myInt = itc.InternalInt; //No function
myInt = itc.GetInt(); //Normal function
myInt = itc.GetInt_Inl(); //Inline function
myInt = itc.GetInt_ForceInl(); //Forced inline function
The resulting assembler code of the setting of myInt (taken from dissassembler):
451 myInt = itc.InternalInt;
0x7ff6fe0d4cae <+0x003e> mov eax,dword ptr [rsp+20h]
0x7ff6fe0d4cb2 <+0x0042> mov dword ptr [rsp+38h],eax
452 myInt = itc.GetInt();
0x7ff6fe0d4cb6 <+0x0046> lea rcx,[rsp+20h]
0x7ff6fe0d4cbb <+0x004b> call nD_Render!ILT+2125(?GetIntInlineTestClassQEAAHXZ) (00007ff6`fe0d1852)
0x7ff6fe0d4cc0 <+0x0050> mov dword ptr [rsp+38h],eax
453 myInt = itc.GetInt_Inl();
0x7ff6fe0d4cc4 <+0x0054> lea rcx,[rsp+20h]
0x7ff6fe0d4cc9 <+0x0059> call nD_Render!ILT+1885(?GetInt_InlInlineTestClassQEAAHXZ) (00007ff6`fe0d1762)
0x7ff6fe0d4cce <+0x005e> mov dword ptr [rsp+38h],eax
454 myInt = itc.GetInt_ForceInl();
0x7ff6fe0d4cd2 <+0x0062> lea rcx,[rsp+20h]
0x7ff6fe0d4cd7 <+0x0067> call nD_Render!ILT+715(?GetInt_ForceInlInlineTestClassQEAAHXZ) (00007ff6`fe0d12d0)
0x7ff6fe0d4cdc <+0x006c> mov dword ptr [rsp+38h],eax
As shown above the setting (of myInt) from the member of InlineTestClass directly is (as expected) 2 mov instructions long. Setting from the GetInt function results in a function call (as expected), however both of the GetInt_Inl and GetInt_ForceInl (inline functions) also result in a function call.
It appears as if the inline function has been compiled as a normal function ignoring the inlining completely (correct me if I am wrong).
This is strange cause according to MSVC documentation:
The inline and __inline specifiers instruct the compiler to insert a copy of the function body into each place the function is called.
Which (I think) would result in:
inline int GetInt_Inl() {return InternalInt; //Is the function body}
myInt = itc.GetInt_Inl(); //Call site
//Should result in
myInt = itc.InternalInt; //Identical to setting from the member directly
Which means that the assembler code should be also identical to the one of setting from the class member directly but it isn't.
Questions:
- Am I missing something or implementing the functions incorrectly?
- Am I interpreting the function of the inline keyword? What is it?
- Why do these inline functions result in a function call?