为什么这个简单的功能没有去虚拟化?
Why doesn't this simple function get de-virtualized?
请考虑以下代码:
struct A {
virtual A& operator+=(const A& other) noexcept = 0;
};
void foo_inner(int *p) noexcept { *p += *p; }
void foo_virtual_inner(A *p) noexcept { *p += *p; }
void foo(int *p) noexcept
{
return foo_inner(p);
}
struct Aint : public A {
int i;
A& operator+=(const A& other) noexcept override final
{
// No devirtualization of foo_virtual with:
i += dynamic_cast<const Aint&>(other).i;
// ... nor with:
// i += reinterpret_cast<const Aint&>(other).i;
return *this;
}
};
void foo_virtual(Aint *p) noexcept
{
return foo_virtual_inner(p);
}
据我所知,foo()
和 foo_virtual()
都应该编译为相同的目标代码。编译器拥有从foo_virtual
调用时,在foo_virtual_inner()
中对operator+=
调用进行去虚拟化所需的所有信息。但是 - GCC 8.3、MSVC 19.10 和 clang 8 都没有这样做。当然,我使用了最大优化标志(-O3
或/Ox
(。
为什么?这是一个错误,还是我错过了什么?
叮当 8 输出:
foo(int*): # @foo(int*)
shl dword ptr [rdi]
ret
foo_virtual(Aint*): # @foo_virtual(Aint*)
mov rax, qword ptr [rdi]
mov rax, qword ptr [rax]
mov rsi, rdi
jmp rax # TAILCALL
GCC 8.3 输出:
foo(int*):
sal DWORD PTR [rdi]
ret
foo_virtual(Aint*):
mov rax, QWORD PTR [rdi]
mov rax, QWORD PTR [rax]
cmp rax, OFFSET FLAT:Aint::operator+=(A const&)
jne .L19
push rbx
xor ecx, ecx
mov edx, OFFSET FLAT:typeinfo for Aint
mov esi, OFFSET FLAT:typeinfo for A
mov rbx, rdi
call __dynamic_cast
test rax, rax
je .L20
mov eax, DWORD PTR [rax+8]
add DWORD PTR [rbx+8], eax
pop rbx
ret
.L19:
mov rsi, rdi
jmp rax
foo_virtual(Aint*) [clone .cold.1]:
.L20:
call __cxa_bad_cast
MSVC 19.10 输出:
p$ = 8
void foo(int * __ptr64) PROC ; foo
mov eax, DWORD PTR [rcx]
add eax, eax
mov DWORD PTR [rcx], eax
ret 0
void foo(int * __ptr64) ENDP ; foo
p$ = 8
void foo_virtual(Aint * __ptr64) PROC ; foo_virtual
mov rax, QWORD PTR [rcx]
mov rdx, rcx
rex_jmp QWORD PTR [rax]
void foo_virtual(Aint * __ptr64) ENDP
PS - 在GCC下编译的代码中对所有这些typeinfo业务的解释是什么?
指向 Aint *p 的实例(但并不认为这一定会发生(,因此它推测性地对 operator+= 的调用进行去虚拟化,并且 typeinfo 检查是它的内联副本。-fno-devirtualize-specullyly导致与Clang和MSVC生成的代码相同。
_Z11foo_virtualP4Aint:
.LFB4:
.cfi_startproc
movq (%rdi), %rax
movq %rdi, %rsi
movq (%rax), %rax
jmp *%rax
按照@JanHubicka的回答,我针对 GCC 提交了一个错误:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89924
并且正在研究(!
编辑:好吧,毕竟我猜它并没有真正被处理:-(
不能假定 Aint* 实际上指向 Aint 对象,直到它看到一些具有未定义语义的操作,否则会引用其非静态成员之一。 否则,这可能是从其他指针类型reinterpret_cast等待reinterpret_casted回该类型的结果。
在我看来,标准转换为 A* 应该是这样的操作,但 AFAICT 标准目前没有这么说。 大意如此的措辞需要考虑转换为正在建造的对象的非虚拟基础,这是故意允许的。
- 在执行其他功能的同时播放动画(LED矩阵和Arduino/ESP8266)
- 多态性和功能结合
- 带内存和隔离功能的SQLite
- 在CMakeLists.txt的安装功能中使用.cmake文件有什么用
- 类模板的成员功能的定义在单独的TU中完全专业化
- 有没有一种方法可以创建一个带有哈希表的数据库,该哈希表具有恒定时间查找功能
- 如何在C++中获得"静态纯虚拟"功能?
- 两个文件使用彼此的功能-如何解决
- 我应该实现右值推送功能吗?我应该使用std::move吗
- QML按钮点击功能执行顺序
- 无法理解此 return 语句的功能,没有它就会发生运行时错误
- 有没有可能有一个只有ADL才能找到的非好友功能
- 功能样式转换从 'int' 到 'ItemType' 的匹配转换
- 文件系统:复制功能的速度秘诀是什么
- 在用于格式4的arm模拟器中实现功能时的一个问题
- 如何在Directwrite中获得给定字体的可用OpenType功能
- 对可变参数使用声明.如何选择正确的功能
- 询问在设计我的手臂模拟器功能表示格式1
- 功能原型的目的
- 为什么这个简单的功能没有去虚拟化?