堆栈分配对象的 C++ 虚拟析构函数内联

C++ virtual destructor inlining for stack allocated objects

是否允许编译器在隐式销毁堆栈分配对象的情况下内联虚拟析构函数?

我理解 'delete' 操作必须通过虚函数 table 调用虚析构函数(例如它不能内联),因为它无法知道确切的 class指针指向。

但是当一个对象被分配到栈上时,编译器知道确切的class。所以我认为可以自由地内联隐式破坏,因为它可以看到 class 的实际析构函数。如果不允许编译器这样做,那为什么不呢?什么场景可以将析构函数覆盖为与编译器所知道的不同的东西?

是的,编译器可以在这种情况下内联虚拟析构函数。让我们考虑一个代码示例:

#include <iostream>

int global = 0;

class A {
public:
  virtual void foo() { std::cout << "A" << std::endl; }
  virtual ~A() { ++global; }
};

class B : public A {
public:
  virtual void foo() { std::cout << "B" << std::endl; }
  virtual ~B() { --global; }
};

int main() {
  {
    B b[5];
    b[0].foo();
  }
  std::cout << "global: " << global << std::endl;
  return 0;
}

https://godbolt.org/g/PWEVW8

如您所见,带有 -O3 优化的 clang 3.8 永远不会为 classes 生成代码(带有 -O3 的 gcc 6.1 将生成 class B,但无论如何都会内联析构函数):

main:                                   # @main
        pushq   %r14
        pushq   %rbx
        pushq   %rax
        movl    std::cout, %edi
        movl    $.L.str.2, %esi
        movl    , %edx
        callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        movq    std::cout(%rip), %rax
        movq    -24(%rax), %rax
        movq    std::cout+240(%rax), %rbx
        testq   %rbx, %rbx
        je      .LBB0_9
        cmpb    [=11=], 56(%rbx)
        je      .LBB0_3
        movb    67(%rbx), %al
        jmp     .LBB0_4
.LBB0_3:
        movq    %rbx, %rdi
        callq   std::ctype<char>::_M_widen_init() const
        movq    (%rbx), %rax
        movl    , %esi
        movq    %rbx, %rdi
        callq   *48(%rax)
.LBB0_4:                                # %_ZNKSt5ctypeIcE5widenEc.exit2
        movsbl  %al, %esi
        movl    std::cout, %edi
        callq   std::basic_ostream<char, std::char_traits<char> >::put(char)
        movq    %rax, %rdi
        callq   std::basic_ostream<char, std::char_traits<char> >::flush()
        movl    std::cout, %edi
        movl    $.L.str, %esi
        movl    , %edx
        callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        movl    global(%rip), %esi
        movl    std::cout, %edi
        callq   std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        movq    %rax, %r14
        movq    (%r14), %rax
        movq    -24(%rax), %rax
        movq    240(%r14,%rax), %rbx
        testq   %rbx, %rbx
        je      .LBB0_9
        cmpb    [=11=], 56(%rbx)
        je      .LBB0_7
        movb    67(%rbx), %al
        jmp     .LBB0_8
.LBB0_7:
        movq    %rbx, %rdi
        callq   std::ctype<char>::_M_widen_init() const
        movq    (%rbx), %rax
        movl    , %esi
        movq    %rbx, %rdi
        callq   *48(%rax)
.LBB0_8:                                # %std::ctype<char>::widen(char) const [clone .exit]
        movsbl  %al, %esi
        movq    %r14, %rdi
        callq   std::basic_ostream<char, std::char_traits<char> >::put(char)
        movq    %rax, %rdi
        callq   std::basic_ostream<char, std::char_traits<char> >::flush()
        xorl    %eax, %eax
        addq    , %rsp
        popq    %rbx
        popq    %r14
        retq
.LBB0_9:
        callq   std::__throw_bad_cast()

        pushq   %rax
        movl    std::__ioinit, %edi
        callq   std::ios_base::Init::Init()
        movl    std::ios_base::Init::~Init(), %edi
        movl    std::__ioinit, %esi
        movl    $__dso_handle, %edx
        popq    %rax
        jmp     __cxa_atexit            # TAILCALL

global:
        .long   0                       # 0x0

.L.str:
        .asciz  "global: "

.L.str.2:
        .asciz  "B"

Is a compiler allowed to inline a virtual destructor in the case of the implicit destruction of stack allocated objects?

是的。只要它调用对象的正确运行时类型的析构函数。

I understand the 'delete' operation must call the virtual destructor via the virtual function table (e.g. it can't inline), since it can't know the exact class the pointer refers to.

只有当编译器无法知道指针所指的具体类型时,才必须进行虚调用。

But when an object is allocated on the stack, the compiler knows the exact class.

正确。

So I would've thought it would be free to inline the implicit destruction since it can see the actual destructors for the class.

嗯,在栈上分配并不能保证析构函数的定义是可见的。但是如果是可见的,那么你的假设是正确的,编译器可以自由内联。