x64汇编学习(8)-- class type & new

-- TOC --

总结:class type对象的this指针,指向对象所在内存块的起始地址,这个地址同时也是对象第1个成员变量的地址。访问对象的成员变量,就是在this指针加上offset。而成员函数,在编译器看来,跟普通函数接口没有太大区别,只是成员函数接口都有1个隐藏的入参 ,第1个入参,this指针。因此,只能通过实例化后的对象来调用成员函数,这样才有this指针。这也就理解了,static成员函数可以通过class name来调用,因为它没有this指针这个隐藏的入参。

测试用C++代码如下:

#include <cstdio>
using namespace std;

struct you{
    int a;
    char b;
    long c;
    you(int a, char b, long c):
        a{a},b{b},c{c}{}
    void show(){
        printf("%d %c %ld\n", a, b, c);
    }
};

int main(){
    you y{1, '2', 3};
    y.show();
    return 0;
}

不开优化,汇编:

you::you(int, char, long) [base object constructor]:
        push    rbp
        mov     rbp, rsp
        # 将第1个参数,对象y的地址,
        # 存入此接口stack的高8bytes,
        # 然后才是入参
        mov     QWORD PTR [rbp-8], rdi
        mov     DWORD PTR [rbp-12], esi
        mov     eax, edx
        mov     QWORD PTR [rbp-24], rcx
        mov     BYTE PTR [rbp-16], al
        # 取对象y的地址到rax
        mov     rax, QWORD PTR [rbp-8]
        # 取参数a到edx
        mov     edx, DWORD PTR [rbp-12]
        # 将参数a存入对象y的起始地址,这个地址也是y.a的地址
        mov     DWORD PTR [rax], edx
        mov     rax, QWORD PTR [rbp-8]
        # 取参数b,无符号扩展到edx
        movzx   edx, BYTE PTR [rbp-16]
        # 将b存入y.b
        mov     BYTE PTR [rax+4], dl
        mov     rax, QWORD PTR [rbp-8]
        mov     rdx, QWORD PTR [rbp-24]
        # 将c存入y.c
        mov     QWORD PTR [rax+8], rdx
        nop
        pop     rbp
        ret
.LC0:
        .string "%d %c %ld\n"
you::show():
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        # 对象y的地址,在此stack frame的高位
        # this指针
        mov     QWORD PTR [rbp-8], rdi
        # 下面的代码,都是取y的地址到rax,
        # 然后通过rax+offset计算a,b,c的地址
        mov     rax, QWORD PTR [rbp-8]
        mov     rcx, QWORD PTR [rax+8]
        mov     rax, QWORD PTR [rbp-8]
        movzx   eax, BYTE PTR [rax+4]
        movsx   edx, al
        mov     rax, QWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rax]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        nop
        leave
        ret
main:
        push    rbp
        mov     rbp, rsp
        # 在stack中开辟16bytes,
        # 这块空间,就是对象y,
        # 从定义来看,对象y需要16bytes空间。
        sub     rsp, 16
        # 取对象y的地址到rax
        lea     rax, [rbp-16]
        mov     ecx, 3
        mov     edx, 50
        mov     esi, 1
        # 调用y的constructor接口,
        # 第1个参数是对象y的地址
        mov     rdi, rax
        call    you::you(int, char, long) [complete object constructor]
        # 调用y的show接口,
        # 虽然show没有参数,但依然要把y的地址传入,
        # 这就是this指针!
        lea     rax, [rbp-16]
        mov     rdi, rax
        call    you::show()
        mov     eax, 0
        leave
        ret

-Wreorder

reorder只是个warning,我想看看当出现此warning的时候,汇编是什么样的。

修改一行代码:

you(int a, char b, long c):
        a{a},c{c},b{b}{}

出现warning,但得到的汇编一模一样!

仔细看看这个warning:

<source>:7:10: warning: 'you::c' will be initialized after [-Wreorder]
    7 |     long c;
      |          ^
<source>:6:10: warning:   'char you::b' [-Wreorder]
    6 |     char b;
      |          ^
<source>:8:5: warning:   when initialized here [-Wreorder]
    8 |     you(int a, char b, long c):
      |     ^~~

you::c will be initialized after char you::b when initialized here...

所以,我理解,constructor要按照申明顺序初始化对象成员变量,因此会有这个warning。只是个warning,不一定就是个错误!


new & delete

继续修改C++代码:

int main(){
    you *y{ new you{1, '2', 3} };
    y->show();
    delete y;
    return 0;
}

汇编如下:

main:
        push    rbp
        mov     rbp, rsp
        push    rbx
        # 扩展24bytes,是为了16bytes对齐
        sub     rsp, 24
        # 16是struct you的size大小
        mov     edi, 16
        call    operator new(unsigned long)
        # 将申请的地址存入rbx
        mov     rbx, rax
        mov     ecx, 3
        mov     edx, 50
        mov     esi, 1
        mov     rdi, rbx
        call    you::you(int, char, long) [complete object constructor]
        mov     QWORD PTR [rbp-24], rbx
        mov     rax, QWORD PTR [rbp-24]
        mov     rdi, rax
        call    you::show()
        mov     rax, QWORD PTR [rbp-24]
        # 检查对象指针是否为0,
        # 如果是,跳过operator delete
        test    rax, rax
        je      .L4
        # operator delete的第2个参数
        mov     esi, 16
        mov     rdi, rax
        call    operator delete(void*, unsigned long)
.L4:
        mov     eax, 0
        # pop rbx
        mov     rbx, QWORD PTR [rbp-8]
        leave
        ret

对operator的调用,也如函数调用一般。

operator delete自带0指针检查,与free(0)不同的是,后者对0的检查,在接口内部。

本文链接:https://cs.pynote.net/hd/asm/202302121/

-- EOF --

-- MORE --