【问题标题】:inline expansion compiles into a function call [c++] [duplicate]内联扩展编译成函数调用 [c++] [重复]
【发布时间】:2018-09-28 21:46:26
【问题描述】:

简介:

我一直在创建一个简单的包装类。我随机发现 (或者看起来是) 一个内联函数仍然编译成一个函数调用。我创建了一个示例类来测试,这就是我发现的:

考虑以下类:

//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;} 
};

这个类有3个函数供参考。

  • GetInt 函数是标准函数。
  • GetInt_Inl 函数是一个内联函数。
  • GetInt_ForceInl 函数是一个确保的内联函数,以防出现 编译器决定不将 GetInt_Inl 实现为内联函数

这样实现:

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

myInt 设置的结果汇编代码(取自反汇编程序):

       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

如上所示,InlineTestClass 成员的设置(myInt)直接是(如预期) 2 mov 指令长。 从 GetInt 函数设置会导致函数调用(如预期),但是 GetInt_InlGetInt_ForceInl (内联函数)也会导致函数调用。

看起来内联函数已被编译为完全忽略内联的普通函数(如果我错了,请纠正我)

根据MSVC documentation,这是一个奇怪的原因:

inline 和 __inline 说明符指示编译器插入一个 将函数体复制到调用函数的每个位置。

(我认为)会导致:

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

这意味着汇编代码也应该与直接从类成员中设置的代码相同,但事实并非如此。

问题:

  1. 我是否遗漏了某些内容或未正确实现功能?
  2. 我是在解释 inline 关键字的功能吗?它是什么?
  3. 为什么这些内联函数会导致函数调用?

【问题讨论】:

  • 如果你在没有优化函数的情况下编译将永远不会被内联。从更改编译器选项开始。 该函数是使用/Od 或/Ob0 编译的。在这些情况下不需要内联。 如果查看您的代码,可以假设 /Od 处于打开状态
  • 您忘记阅读下一段:插入(称为内联扩展或内联)只有在编译器的成本/收益分析表明它是有利可图的情况下才会发生。内联扩展以更大的代码大小为代价减轻了函数调用开销。inline 只是建议内联函数。
  • 另外:编译器将内联扩展选项和关键字视为建议。不能保证函数会被内联。即使使用 __forceinline 关键字,您也不能强制编译器内联特定函数。
  • 请注意,如果您进行调试构建,MSVC 无论如何都不会内联。 (非内联通常更容易通过调试器完成)。

标签: c++ assembly visual-c++ inline


【解决方案1】:

默认情况下,类中定义的函数是“推荐内联”的。所以 inline 绝对什么都不做。此外,编译器始终可以随意否决程序员的关键字。这只是建议性的。

来自 C++17 草案(第 147 页):

inline 说明符向实现表明,在调用点对函数体进行内联替换优于通常的函数调用机制。在调用点执行此内联替换不需要实现;然而,即使省略了这个内联替换,本小节中指定的内联函数的其他规则仍应遵守。

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

【讨论】:

    猜你喜欢
    • 2010-10-13
    • 2020-07-25
    • 2012-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多