9

我正在使用特定于 MS 的关键字来强制内联全局函数,但我注意到如果该函数使用具有显式平凡析构函数的对象,则该函数无法内联自身。

引用MSDN

即使使用__forceinline,编译器也不能在所有情况下内联代码。如果出现以下情况,编译器不能内联函数:

  • 该函数或其调用者是用/Ob0(调试构建的默认选项)编译的。

  • 函数和调用者使用不同类型的异常处理(一种是 C++ 异常处理,另一种是结构化异常处理)。

  • 该函数有一个可变参数列表。

  • 该函数使用内联汇编,除非使用/Og/Ox/O1或编译/O2

  • 该函数是递归的,不伴随#pragma inline_recursion(on). 使用 pragma,递归函数被内联到 16 个调用的默认深度。要减少内联深度,请使用inline_depthpragma。

  • 该函数是虚拟的,并且被虚拟调用。可以内联对虚拟函数的直接调用。

  • 程序获取函数的地址,并通过指向函数的指针进行调用。可以内联对已获取地址的函数的直接调用。

  • 该函数还标有裸__declspec修饰符。

我正在尝试以下自包含程序来测试行为

#include <iostream>
#define INLINE __forceinline
template <class T>
struct rvalue
{
    T& r_;
    explicit INLINE rvalue(T& r) : r_(r) {}
};

template <class T>
INLINE
T movz(T& t)
{
    return T(rvalue<T>(t));
}
template <class T>
class Spam
{
public:
    INLINE operator rvalue<Spam>()  { return rvalue<Spam>(*this); }
    INLINE Spam() : m_value(0)  {}
    INLINE Spam(rvalue<Spam> p) : m_value(p.r_.m_value) {}
    INLINE Spam& operator= (rvalue<Spam> p) 
    {
        m_value = p.r_.m_value;
        return *this; 
    }
    INLINE explicit Spam(T value) : m_value(value) {    }
    INLINE operator T() { return m_value; };
    template <class U, class E> INLINE  Spam& operator= (Spam<U> u) { return *this; }
    INLINE ~Spam() {}
private:
    Spam(Spam<T>&); // not defined
    Spam& operator= (Spam&); // not defined
private:
    T m_value; 
};
INLINE int foo()
{
    Spam<int> p1(int(5)), p2;
    p2 = movz(p1);
    return p2;
}

int main()
{
    std::cout << foo() << std::endl;
} 

有了简单的析构函数INLINE ~Spam() {},我们有以下反汇编

int main()
{
000000013F4B1010  sub         rsp,28h  
    std::cout << foo() << std::endl;
000000013F4B1014  lea         rdx,[rsp+30h]  
000000013F4B1019  lea         rcx,[rsp+38h]  
000000013F4B101E  mov         dword ptr [rsp+30h],5  
000000013F4B1026  call        movz<Spam<int> > (013F4B1000h)  
000000013F4B102B  mov         rcx,qword ptr [__imp_std::cout (013F4B2050h)]  
000000013F4B1032  mov         edx,dword ptr [rax]  
000000013F4B1034  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (013F4B2040h)]  
000000013F4B103A  mov         rdx,qword ptr [__imp_std::endl (013F4B2048h)]  
000000013F4B1041  mov         rcx,rax  
000000013F4B1044  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (013F4B2058h)]  
}

在没有析构函数的情况下,INLINE ~Spam() {}我们有以下反汇编

int main()
{
000000013FF01000  sub         rsp,28h  
    std::cout << foo() << std::endl;
000000013FF01004  mov         rcx,qword ptr [__imp_std::cout (013FF02050h)]  
000000013FF0100B  mov         edx,5  
000000013FF01010  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (013FF02040h)]  
000000013FF01016  mov         rdx,qword ptr [__imp_std::endl (013FF02048h)]  
000000013FF0101D  mov         rcx,rax  
000000013FF01020  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (013FF02058h)]  
}
000000013FF01026  xor         eax,eax  
}

我无法理解,为什么在存在析构函数的情况下,编译器无法内联函数T movz(T& t)

  • 注意行为从 2008 年到 2013 年是一致的
  • 注意我检查了 cygwin-gcc 但编译器确实内联了代码。我目前无法验证其他编译器,但如果需要,我会在接下来的 12 小时内更新
4

1 回答 1

1

是的,这是一个错误。我已经在 Qt over MinGW 编译器环境上对其进行了测试。它很好地优化了一切。

首先,为了更容易查看汇编代码,我对您的代码进行了一些更改,如下所示:

int main()
{
    int i = foo();
    std::cout << i << std::endl;
}

从我的 Qt 的调试反汇编中:

        45  int main()
        46  {
0x401600                    lea    0x4(%esp),%ecx
0x401604  <+0x0004>         and    $0xfffffff0,%esp
0x401607  <+0x0007>         pushl  -0x4(%ecx)
0x40160a  <+0x000a>         push   %ebp
0x40160b  <+0x000b>         mov    %esp,%ebp
0x40160d  <+0x000d>         push   %ecx
0x40160e  <+0x000e>         sub    $0x54,%esp
0x401611  <+0x0011>         call   0x402160 <__main>
0x401616  <+0x0016>         movl   $0x5,-0x10(%ebp)
        47      int i = foo();
0x401683  <+0x0083>         mov    %eax,-0xc(%ebp)
        48      std::cout << i << std::endl;
0x401686  <+0x0086>         mov    -0xc(%ebp),%eax
0x401689  <+0x0089>         mov    %eax,(%esp)
0x40168c  <+0x008c>         mov    $0x6fcba2c0,%ecx
0x401691  <+0x0091>         call   0x401714 <_ZNSolsEi>
0x401696  <+0x0096>         sub    $0x4,%esp
0x401699  <+0x0099>         movl   $0x40171c,(%esp)
0x4016a0  <+0x00a0>         mov    %eax,%ecx
0x4016a2  <+0x00a2>         call   0x401724 <_ZNSolsEPFRSoS_E>
0x4016a7  <+0x00a7>         sub    $0x4,%esp
        49  }
0x4016aa  <+0x00aa>         mov    $0x0,%eax
0x4016af  <+0x00af>         mov    -0x4(%ebp),%ecx
0x4016b2  <+0x00b2>         leave
0x4016b3  <+0x00b3>         lea    -0x4(%ecx),%esp
0x4016b6  <+0x00b6>         ret

您甚至可以看到 foo() 已经过优化。可以看到变量 'i' 直接赋值给 5 并打印出来。

于 2014-09-19T11:59:56.640 回答