• [Rust] 动态分发的特征对象(&dyn trait / Box<dyn trait>)


    trait GetInt {
        fn get_int(&self) -> u64;
    }
    
    struct WhyNotU8 {// vtable stored at section L__unnamed_1
        x: u8
    }
    
    struct ActualU64 {// vtable stored at section L__unnamed_2
        x: u64
    }
    
    impl GetInt for WhyNotU8 {
        fn get_int(&self) -> u64 {
            self.x as u64
        } 
    } 
    
    impl GetInt for ActualU64 {
        fn get_int(&self) -> u64 {
            self.x
        }
    }
    
    
    // `&dyn`表明我们要使用动态分发,而不是单态化. 因此在生成的汇编中只有一个`retrieve_int`函数.
    // 若此处使用泛型,那么在生成的汇编中,对每个实现GetInt的类型都有一个相应的`retrieve_int`函数 
    pub fn retrieve_int(u: &dyn GetInt) { // <<<<<<<<<<<<<<<
       
      	// 在汇编中, 我们仅仅调用传入rsi中的地址(对应get_int的地址)
        //并期望在调用即将发生前, rsi被正确设置
        let x = u.get_int();
    }
    
    pub fn do_call() {
       
      // 即便 `WhyNotU8` 和 `ActualU64` 对应的vtable表项中有 `core::ptr::real_drop_in_place`的函数指针, 这个函数也从来不被实际调用. 
        let a = WhyNotU8 { x: 0 };
        let b = ActualU64 { x: 0 };
    
        retrieve_int(&a);
        retrieve_int(&b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    汇编: -C opt-level=0

    core::ptr::drop_in_place: 
      retq 
    
    core::ptr::drop_in_place: 
      retq 
    
    ::get_int: 
      movzbl (%rdi), %eax  ; 将get_int的第1参数(引用/指针)所指向的数据 放入eax作为返回值 
      retq 
    
    ::get_int: 
      movq (%rdi), %rax ; 将get_int的第1参数(引用/指针)所指向的数据 放入rax作为返回值 
      retq 
    
    example::retrieve_int:  ;; fn retrieve_int(u: &dyn GetInt) -> ()
      pushq %rax   ; 为了满足规定:call之前stack是按16字节对齐的(上一次call压入的8字节返回地址破坏了这点)
      callq *24(%rsi)  ; rsi 为每个Table表项的起始地址
      				 ; rsi + 24处存放着指向 函数::get_int 的指针 
     				 ; call *24(%rsi) = call *(rsi+24) ==> 调用对应的get_int()函数 	
     									
      popq %rax  
      retq
    
    example::do_call: 
      subq $24, %rsp  	; 在stack上分配 24 字节 
      movb $0, 15(%rsp) 	; let b = ActualU64 { x: 0 };
      movq $0, 16(%rsp) 	; let a = WhyNotU8 { x: 0 };
      leaq 15(%rsp), %rdi 	; rdi <- rsp + 15  将对b的引用作为第一参数
      leaq .L__unnamed_1(%rip), %rsi 		; rsi <- 表项.L__unnamed_1的起始地址
      callq *example::retrieve_int@GOTPCREL(%rip)  	; 调用 retrieve_int()
      leaq 16(%rsp), %rdi 		; rdi <- rsp + 16  将对a的引用作为第一参数
      leaq .L__unnamed_2(%rip), %rsi 		; rsi <- 表项.L__unnamed_2的起始地址
      callq *example::retrieve_int@GOTPCREL(%rip)		; 调用 retrieve_int()
      addq $24, %rsp 	  ; 归还 stack 上空间
      retq 
    
    
    ;;; Table: 
    .L__unnamed_1: ; 此处地址 + 24 字节 == 指向get_int()的指针 
      .quad core::ptr::drop_in_place ; 8 字节 
      .asciz "\001\000\000\000\000\000\000\000\001\000\000\000\000\000\000" ; 16 = 15 + 1 字节
      .quad ::get_int ; 8 字节, 该对象对应的get_int()地址 
    
    
    .L__unnamed_2: ; 此处地址 + 24 字节 == 指向get_int()的指针 
      .quad core::ptr::drop_in_place  ; 8 字节  
      .asciz "\b\000\000\000\000\000\000\000\b\000\000\000\000\000\000" ; 16 = 15 + 1 字节
      .quad ::get_int  ; 8 字节, 该对象对应的get_int()地址
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    更进一步, 改写为静态分派做对比:
    能发现上面采用动态分派时只生成了一个retrieve_int() . 而改成静态分派后分别对于两个类型 (WhyNotU8, ActualU64) 都生成了相应的 retrieve_int_static() , 并且没有产生 vtable .

    // ... 省略相同代码 ... 
    fn retrieve_int_static(u:&impl GetInt){
         let x = u.get_int();
    }
    
    pub fn do_call() {
       
        let a = WhyNotU8 { x: 0 };
        let b = ActualU64 { x: 0 };
    
        retrieve_int_static(&a);
        retrieve_int_static(&b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    对应汇编:

    ::get_int:
            movzbl  (%rdi), %eax
            retq
    
    ::get_int:
            movq    (%rdi), %rax
            retq
    
    example::retrieve_int_static:  # 编译时为WhyNotU8生成的retrieve_int_static
            pushq   %rax
            callq   *::get_int@GOTPCREL(%rip)
            popq    %rax
            retq
    
    example::retrieve_int_static:  # 编译时为ActualU64生成的retrieve_int_static
            pushq   %rax
            callq   *::get_int@GOTPCREL(%rip)
            popq    %rax
            retq
    
    example::do_call:
            subq    $24, %rsp
            movb    $0, 15(%rsp)
            movq    $0, 16(%rsp)
            leaq    15(%rsp), %rdi
            callq   example::retrieve_int_static
            leaq    16(%rsp), %rdi
            callq   example::retrieve_int_static
            addq    $24, %rsp
            retq
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    参考:

    为什么要push rax?

    关于call之前用push reg进行栈对齐的补充:

    假如把上面这个函数改成有返回值的:

    pub fn retrieve_int(u: &dyn GetInt)->u64 {
    
    • 1
    example::retrieve_int:
      pushq   %rax
      callq   *24(%rsi)
      movq    %rax, (%rsp)
      movq    (%rsp), %rax ;; 设置了返回值rax
      popq    %rcx 
      ;; !!不能再是popq %rax了,会破坏返回值!! 
      ;; 为了抵消对齐用的push %rax, 应popq到一个当前无用的寄存器rcx
      retq
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    ez_pz_hackover_2016
    CI/CD 持续集成与持续交付(1)
    11.目标分割
    docker:harbor私有仓库
    推荐系统[八]算法实践总结V2:排序学习框架(特征提取标签获取方式)以及京东推荐算法精排技术实战
    人工智能导论
    windows上 adb devices有设备 wsl上没有
    Abnova丨DNA 标记高质量控制测试方案
    Crypto(5)2023xctf ezCrypto(待补)
    Vue3二维码生成(简洁明了)
  • 原文地址:https://blog.csdn.net/weixin_42338512/article/details/126343983