-2

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:

  1. Am I missing something or implementing the functions incorrectly?
  2. Am I interpreting the function of the inline keyword? What is it?
  3. Why do these inline functions result in a function call?
Jakubek K.
  • 31
  • 3
  • 4
    if you compile without optimization functions will be never inlined. begin from change compiler options. *The function is compiled by using /Od or /Ob0. No inlining is expected in these cases.* can assume that `/Od` is on if look on your code – RbMm Sep 28 '18 at 21:50
  • 2
    You forgot to read the next paragraph: *The insertion (called inline expansion or inlining) occurs only if the compiler's cost/benefit analysis show it to be profitable. Inline expansion alleviates the function-call overhead at the potential cost of larger code size.*. `inline` is only a suggestion that the function be inlined. – NathanOliver Sep 28 '18 at 21:54
  • 1
    additionally: *The compiler treats the inline expansion options and keywords as suggestions. There is no guarantee that functions will be inlined. You cannot force the compiler to inline a particular function, even with the __forceinline keyword.* – Swordfish Sep 28 '18 at 21:54
  • Also: [Why should I ever use inline code?](https://stackoverflow.com/q/132738/7571258) – zett42 Sep 28 '18 at 22:41
  • And note that if you do a debug build MSVC will not inline regardless. (Non-inline is generally far easier to walk through with the debugger). – SoronelHaetir Sep 28 '18 at 22:51

1 Answers1

2

Functions defined within classes are 'recommended inline' by default. So inline there does absolutely nothing. Also, the compiler is always free to overrule the programmer's keyword regardless. It's merely advisory.

From the C++17 draft (page 147):

The inline specifier indicates to the implementation that inline substitution of the function body at the point of call is to be preferred to the usual function call mechanism. An implementation is not required to perform this inline substitution at the point of call; however, even if this inline substitution is omitted, the other rules for inline functions specified in this subclause shall still be respected.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4713.pdf