让我们尝试一下 g++ 目标 x86:
$ cat y.cpp
struct A
{
virtual void not_used(int);
virtual void f(int);
};
void foo(A &a)
{
for (unsigned i = 0; i < 1000; ++i)
a.f(13);
}
$
$ gcc -S -O3 y.cpp # assembler output, max optimization
$
$ cat y.s
.file "y.cpp"
.section .text.unlikely,"ax",@progbits
.LCOLDB0:
.text
.LHOTB0:
.p2align 4,,15
.globl _Z3fooR1A
.type _Z3fooR1A, @function
_Z3fooR1A:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
pushq %rbx
.cfi_def_cfa_offset 24
.cfi_offset 3, -24
movq %rdi, %rbp
movl $1000, %ebx
subq $8, %rsp
.cfi_def_cfa_offset 32
.p2align 4,,10
.p2align 3
.L2:
movq 0(%rbp), %rax
movl $13, %esi
movq %rbp, %rdi
call *8(%rax)
subl $1, %ebx
jne .L2
addq $8, %rsp
.cfi_def_cfa_offset 24
popq %rbx
.cfi_def_cfa_offset 16
popq %rbp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size _Z3fooR1A, .-_Z3fooR1A
.section .text.unlikely
.LCOLDE0:
.text
.LHOTE0:
.ident "GCC: (GNU) 5.3.1 20160406 (Red Hat 5.3.1-6)"
.section .note.GNU-stack,"",@progbits
$
L2 标签是循环的顶部。 L2 之后的行似乎正在将 vpointer 加载到 rax 中。 L2 之后的第 4 行调用似乎是间接的,从 vstruct 中获取指向 f() 覆盖的指针。
我对此感到惊讶。我本来希望编译器将 f() 覆盖函数的地址视为循环不变量。似乎 gcc 正在做出两个“偏执”的假设:
- f() 覆盖函数可能会更改对象中的隐藏 vpointer
不知何故,或
- f() 覆盖函数可能会更改
vstruct 以某种方式。
编辑:在一个单独的编译单元中,我实现了 A::f() 和一个调用 foo() 的主函数。然后,我使用链接时优化使用 gcc 构建了一个可执行文件,并在其上运行了 objdump。虚函数调用是内联的。所以,也许这就是为什么没有 LTO 的 gcc 优化不如预期的理想。