是否为实现特征的所有类型生成 vtables?
Are vtables generated for all types that implement a trait?
如果我有特质Foo
,一些实现者Bar
,Baz
。
impl Foo for Bar {
}
impl Foo for Baz {
}
但是假设我只使用其中一个作为特征对象,
let bar = Bar {..};
let foo: &dyn Foo = &bar;
那么我的二进制文件还会有两者的 vtable 吗?
此行为在调试版本和发布版本之间是否会发生变化?
让我们一探究竟。我把这个类似的代码 in the Rust Playground 和 运行 “Show Assembly”:
trait Foo {
fn x(&self);
}
impl Foo for u8 {
fn x(&self) {
dbg!("xu8");
}
}
impl Foo for u16 {
fn x(&self) {
dbg!("xu16");
}
}
fn main() {
let foo: &dyn Foo = &123_u8;
foo.x();
123_u8.x();
123_u16.x();
}
在(调试模式)汇编输出中,main
是:
playground::main:
subq , %rsp
leaq .L__unnamed_12(%rip), %rax
movq %rax, 8(%rsp)
leaq .L__unnamed_2(%rip), %rax
movq %rax, 16(%rsp)
leaq .L__unnamed_12(%rip), %rdi
callq *.L__unnamed_2+24(%rip)
leaq .L__unnamed_12(%rip), %rdi
callq <u8 as playground::Foo>::x
leaq .L__unnamed_13(%rip), %rdi
callq <u16 as playground::Foo>::x
addq , %rsp
retq
我们不需要读懂x86汇编的每一个细节就可以看出这里有三个函数调用,最后两个是我为了对比添加的静态调用。所以,.L__unnamed_2
可能与虚表有关。那是什么?
.L__unnamed_2:
.quad core::ptr::drop_in_place<u8>
.asciz "[=12=]1[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0[=12=]1[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0"
.quad <u8 as playground::Foo>::x
对我来说看起来像一个 vtable:它指的是滴胶和 x()
的实现。但是没有什么对 u16
做同样的事情——对 <u16 as playground::Foo>::x
的唯一引用是 main
.
中的静态调度调用
当然,这也不排除编译器生成了vtable数据,然后在到达汇编列表之前就把它扔掉了。但如果确实如此,那么这要么是编译器性能错误,要么就是便宜到不值得担心。
(此外,作为更多轶事证据:Rust 编译器是 known to generate multiple vtables for the same type 如果它们恰好在单独的代码生成单元中需要。)
如果我有特质Foo
,一些实现者Bar
,Baz
。
impl Foo for Bar {
}
impl Foo for Baz {
}
但是假设我只使用其中一个作为特征对象,
let bar = Bar {..};
let foo: &dyn Foo = &bar;
那么我的二进制文件还会有两者的 vtable 吗? 此行为在调试版本和发布版本之间是否会发生变化?
让我们一探究竟。我把这个类似的代码 in the Rust Playground 和 运行 “Show Assembly”:
trait Foo {
fn x(&self);
}
impl Foo for u8 {
fn x(&self) {
dbg!("xu8");
}
}
impl Foo for u16 {
fn x(&self) {
dbg!("xu16");
}
}
fn main() {
let foo: &dyn Foo = &123_u8;
foo.x();
123_u8.x();
123_u16.x();
}
在(调试模式)汇编输出中,main
是:
playground::main:
subq , %rsp
leaq .L__unnamed_12(%rip), %rax
movq %rax, 8(%rsp)
leaq .L__unnamed_2(%rip), %rax
movq %rax, 16(%rsp)
leaq .L__unnamed_12(%rip), %rdi
callq *.L__unnamed_2+24(%rip)
leaq .L__unnamed_12(%rip), %rdi
callq <u8 as playground::Foo>::x
leaq .L__unnamed_13(%rip), %rdi
callq <u16 as playground::Foo>::x
addq , %rsp
retq
我们不需要读懂x86汇编的每一个细节就可以看出这里有三个函数调用,最后两个是我为了对比添加的静态调用。所以,.L__unnamed_2
可能与虚表有关。那是什么?
.L__unnamed_2:
.quad core::ptr::drop_in_place<u8>
.asciz "[=12=]1[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0[=12=]1[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0[=12=]0"
.quad <u8 as playground::Foo>::x
对我来说看起来像一个 vtable:它指的是滴胶和 x()
的实现。但是没有什么对 u16
做同样的事情——对 <u16 as playground::Foo>::x
的唯一引用是 main
.
当然,这也不排除编译器生成了vtable数据,然后在到达汇编列表之前就把它扔掉了。但如果确实如此,那么这要么是编译器性能错误,要么就是便宜到不值得担心。
(此外,作为更多轶事证据:Rust 编译器是 known to generate multiple vtables for the same type 如果它们恰好在单独的代码生成单元中需要。)