【发布时间】: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_Inl 和 GetInt_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
这意味着汇编代码也应该与直接从类成员中设置的代码相同,但事实并非如此。
问题:
- 我是否遗漏了某些内容或未正确实现功能?
- 我是在解释 inline 关键字的功能吗?它是什么?
- 为什么这些内联函数会导致函数调用?
【问题讨论】:
-
如果你在没有优化函数的情况下编译将永远不会被内联。从更改编译器选项开始。 该函数是使用/Od 或/Ob0 编译的。在这些情况下不需要内联。 如果查看您的代码,可以假设
/Od处于打开状态 -
您忘记阅读下一段:插入(称为内联扩展或内联)只有在编译器的成本/收益分析表明它是有利可图的情况下才会发生。内联扩展以更大的代码大小为代价减轻了函数调用开销。。
inline只是建议内联函数。 -
另外:编译器将内联扩展选项和关键字视为建议。不能保证函数会被内联。即使使用 __forceinline 关键字,您也不能强制编译器内联特定函数。
-
请注意,如果您进行调试构建,MSVC 无论如何都不会内联。 (非内联通常更容易通过调试器完成)。
标签: c++ assembly visual-c++ inline