实现 trait 的所有类型都会生成 vtable 吗?

11
如果我有一个名为Foo的特质,并且一些实现者是BarBaz
impl Foo for Bar {
}

impl Foo for Baz {
}

但是假设我只使用其中一个作为特质对象,

let bar = Bar {..};
let foo: &dyn Foo = &bar;

那么我的二进制代码仍然会拥有两个虚函数表吗? 这种行为在调试版和发布版之间是否改变?

1个回答

8

让我们来找出答案。我在 Rust Playground 中放置了类似的代码 (链接),并运行了“显示汇编”:

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    $24, %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    $24, %rsp
    retq

我们不需要能够阅读x86汇编的每个细节,就可以看到这里有三个函数调用,其中后面两个是我添加的静态调用,用于比较。因此,.L__unnamed_2可能与虚表有关。那是什么?
.L__unnamed_2:
    .quad   core::ptr::drop_in_place<u8>
    .asciz  "\001\000\000\000\000\000\000\000\001\000\000\000\000\000\000"
    .quad   <u8 as playground::Foo>::x

看起来像是一个虚函数表:它引用了 drop glue 和 x() 的实现。但是对于 u16 没有类似的内容——唯一引用 <u16 as playground::Foo>::x 的地方是在 main 中静态分发的调用。

当然,这并不排除编译器生成了虚函数表数据,然后在汇编列表之前丢弃了它。但如果是这样,那么要么这将是一个编译器性能 bug,要么就是非常便宜,不值得担心。

(此外,作为更多的轶事证据:如果它们恰好在单独的代码生成单元中需要,Rust 编译器 已知会为同一类型生成多个虚函数表。)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接