简略的继承C++编译器是怎么把逻辑上是继承关系的C++代码,父类和子类,怎么用汇编体现这种关系呢?

老规矩,先上C++代码:

#include<iostream>/** * 简略继承*/class Father{public:    int a;    Father(){        a = 11;    }    ~Father(){        a = 22;    }protected:    int b;private:    int c;};class Son : public Father{public:    int s;    Son(){        s = 66;    }};int main(){    Son son;    son.a = 888;    return 0;}

汇编是如何体现的呢?

        .file    "3-object-class-inherit-0.cpp"    .text    .section    .rodata    .type    _ZStL19piecewise_construct, @object    .size    _ZStL19piecewise_construct, 1_ZStL19piecewise_construct:    .zero    1    .local    _ZStL8__ioinit    .comm    _ZStL8__ioinit,1,1    .section    .text._ZN6FatherC2Ev,"axG",@progbits,_ZN6FatherC5Ev,comdat    .align 2    .weak    _ZN6FatherC2Ev    .type    _ZN6FatherC2Ev, @function_ZN6FatherC2Ev:.LFB1523:    .cfi_startproc    endbr64    pushq    %rbp    .cfi_def_cfa_offset 16    .cfi_offset 6, -16    movq    %rsp, %rbp    .cfi_def_cfa_register 6    movq    %rdi, -8(%rbp) # 把main函数的rbp-48这个值,放到 rbp-8    movq    -8(%rbp), %rax # 把main函数的rbp-48这个值,放到rax寄存器    movl    $11, (%rax) # 把11放到main函数的rbp-48这个值所指向的内存处(弄了半天,绕这么大弯子,就是想让父类构造函数的赋值语句,赋值到main函数栈帧所指向的内存,因为编译器无奈间接算出父类成员变量a的内存地址,所以只能用传值的形式一路传过来)    nop    popq    %rbp    .cfi_def_cfa 7, 8    ret    .cfi_endproc.LFE1523:    .size    _ZN6FatherC2Ev, .-_ZN6FatherC2Ev    .weak    _ZN6FatherC1Ev    .set    _ZN6FatherC1Ev,_ZN6FatherC2Ev    .section    .text._ZN6FatherD2Ev,"axG",@progbits,_ZN6FatherD5Ev,comdat    .align 2    .weak    _ZN6FatherD2Ev    .type    _ZN6FatherD2Ev, @function_ZN6FatherD2Ev:.LFB1526:    .cfi_startproc    endbr64    pushq    %rbp    .cfi_def_cfa_offset 16    .cfi_offset 6, -16    movq    %rsp, %rbp    .cfi_def_cfa_register 6    movq    %rdi, -8(%rbp)    movq    -8(%rbp), %rax    movl    $22, (%rax) # 把22放到main函数栈帧rbp-48这个值,这是变量a的内存地址,放到a所在的内存地址处。留神(%rax)是指rax寄存器外面的数值所指向的内存,而%rax是指寄存器    nop    popq    %rbp    .cfi_def_cfa 7, 8    ret    .cfi_endproc.LFE1526:    .size    _ZN6FatherD2Ev, .-_ZN6FatherD2Ev    .weak    _ZN6FatherD1Ev    .set    _ZN6FatherD1Ev,_ZN6FatherD2Ev    .section    .text._ZN3SonC2Ev,"axG",@progbits,_ZN3SonC5Ev,comdat    .align 2    .weak    _ZN3SonC2Ev    .type    _ZN3SonC2Ev, @function_ZN3SonC2Ev:.LFB1529:    .cfi_startproc    endbr64    pushq    %rbp    .cfi_def_cfa_offset 16    .cfi_offset 6, -16    movq    %rsp, %rbp    .cfi_def_cfa_register 6    subq    $16, %rsp # 给Son子类的构造函数分配内存    movq    %rdi, -8(%rbp) # 把main函数的rbp-48这个值放到Son构造函数的rbp-8内存里    movq    -8(%rbp), %rax # 把main函数的rbp-48这个值放到rax    movq    %rax, %rdi # 把main函数的rbp-48这个值放到rdi寄存器用作传参数,在子构造函数里调用父构造函数,能够看出,是先调用的父构造函数的逻辑,再执行子构造函数的代码逻辑    call    _ZN6FatherC2Ev # 调用父构造函数    movq    -8(%rbp), %rax    movl    $66, 12(%rax) # 执行子构造函数的代码逻辑 rax+12 == rbp-48+12 == rbp-36    nop    leave    .cfi_def_cfa 7, 8    ret    .cfi_endproc.LFE1529:    .size    _ZN3SonC2Ev, .-_ZN3SonC2Ev    .weak    _ZN3SonC1Ev    .set    _ZN3SonC1Ev,_ZN3SonC2Ev    .section    .text._ZN3SonD2Ev,"axG",@progbits,_ZN3SonD5Ev,comdat    .align 2    .weak    _ZN3SonD2Ev    .type    _ZN3SonD2Ev, @function_ZN3SonD2Ev:.LFB1533:    .cfi_startproc    endbr64    pushq    %rbp    .cfi_def_cfa_offset 16    .cfi_offset 6, -16    movq    %rsp, %rbp    .cfi_def_cfa_register 6    subq    $16, %rsp # 调配栈桢    movq    %rdi, -8(%rbp) # # 把main函数栈帧rbp-48这个值,这是变量a的内存地址,放到rbp-8内存处    movq    -8(%rbp), %rax    movq    %rax, %rdi # 计算main函数栈帧rbp-48这个值,这是变量a的内存地址,父类析构函数用到它    call    _ZN6FatherD2Ev    nop    leave    .cfi_def_cfa 7, 8    ret    .cfi_endproc.LFE1533:    .size    _ZN3SonD2Ev, .-_ZN3SonD2Ev    .weak    _ZN3SonD1Ev    .set    _ZN3SonD1Ev,_ZN3SonD2Ev    .text    .globl    main    .type    main, @functionmain:.LFB1531:    .cfi_startproc    endbr64    pushq    %rbp    .cfi_def_cfa_offset 16    .cfi_offset 6, -16    movq    %rsp, %rbp    .cfi_def_cfa_register 6    pushq    %rbx    subq    $40, %rsp # 为main调配40字节栈帧    .cfi_offset 3, -24    movq    %fs:40, %rax    movq    %rax, -24(%rbp)    xorl    %eax, %eax    leaq    -48(%rbp), %rax # 这不是超出栈了吗?    movq    %rax, %rdi # 把rbp-48这个值传到结构函数参数?(这里构造函数是无参结构啊)留神:无参结构只是C++语法个性上的说法,汇编里,用到什么数据就传什么数据,具体用到什么,编译器本人计算    call    _ZN3SonC1Ev    movl    $888, -48(%rbp) # 父类的成员变量是在main函数的栈帧里调配的,该对象是main函数的局部变量,所以在栈上调配,如果是全局变量,就不在栈上调配了    movl    $0, %ebx    leaq    -48(%rbp), %rax # 计算main函数栈帧rbp-48这个值,这是变量a的内存地址,是为了一路传给父类析构函数用的    movq    %rax, %rdi #     call    _ZN3SonD1Ev # 析构函数    movl    %ebx, %eax    movq    -24(%rbp), %rdx    xorq    %fs:40, %rdx    je    .L7    call    __stack_chk_fail@PLT.L7:    addq    $40, %rsp    popq    %rbx    popq    %rbp    .cfi_def_cfa 7, 8    ret    .cfi_endproc.LFE1531:    .size    main, .-main    .type    _Z41__static_initialization_and_destruction_0ii, @function_Z41__static_initialization_and_destruction_0ii:.LFB2015:    .cfi_startproc    endbr64    pushq    %rbp    .cfi_def_cfa_offset 16    .cfi_offset 6, -16    movq    %rsp, %rbp    .cfi_def_cfa_register 6    subq    $16, %rsp    movl    %edi, -4(%rbp)    movl    %esi, -8(%rbp)    cmpl    $1, -4(%rbp)    jne    .L10    cmpl    $65535, -8(%rbp)    jne    .L10    leaq    _ZStL8__ioinit(%rip), %rdi    call    _ZNSt8ios_base4InitC1Ev@PLT    leaq    __dso_handle(%rip), %rdx    leaq    _ZStL8__ioinit(%rip), %rsi    movq    _ZNSt8ios_base4InitD1Ev@GOTPCREL(%rip), %rax    movq    %rax, %rdi    call    __cxa_atexit@PLT.L10:    nop    leave    .cfi_def_cfa 7, 8    ret    .cfi_endproc.LFE2015:    .size    _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii    .type    _GLOBAL__sub_I_main, @function

上述汇编,我删掉了一点点开端的非核心汇编,避免篇幅太长。

下面汇编代码从main函数开始剖析,你会发现,因为Son类的实例化对象是在main函数外部创立的,属于main函数外部的局部变量,所以该对象的成员变量的内存,都是在main函数栈帧上调配的,若rbp存的是main函数的栈帧地址,那么a是在rbp-48处调配的内存,rbp-48+12即rbp-36是s变量的地址,这些都是编译器本人计算出来的绝对地址,想对于rbp栈帧的地址。

【我的疑难是,rsp明明指向的是rbp-40处,怎么能在rbp-48处给a分配内存,这不是超出栈首地址的范畴了吗?须要摸索】

而后,我从汇编上看出,原来就是先调用的父类构造函数的代码逻辑,而后再调用子类构造函数的代码逻辑,这个先后顺序在汇编上也是这么干的。

总之通过上述剖析发现,尽管c++继承很简单,波及到父类变量与子类变量,波及到先调用父类的构造函数,再调用子类构造函数等等,但实际上还是变量调配在哪的事。父类成员变量和子类成员变量最终都要落到内存某个地位上,而这里的变量,因为对象是在main函数里的局部变量,所以它的成员变量都是栈帧上调配的。而且,所谓的对象,只是个抽象概念,汇编里并没有对象这个概念,有的只是对象外部的那些成员变量以及它们对应的内存地址,操作对象,最终操作的也只是对象内的成员变量,它和函数外部的部分整型int变量没有任何区别。

因而,编译器所干的事,就是读懂你的C++代码以及相干对象、类与类之间的继承关系,而后决定,我去哪里调配相干的成员变量的内存,计算好这些变量的地址,最初体现到汇编里,也只是操作内存地址中的数据而已,只有内存地址算对了,剩下的只是加减乘除去对内存里存储的数据进行运算。即我该去哪块内存调配这些成员变量的存储空间,以及我该用什么代码逻辑去解决这些存储空间中的数据。