关于c++:C如何用简单的汇编指令实现C复杂抽象的面向对象概念3重载与重写

3次阅读

共计 2318 个字符,预计需要花费 6 分钟才能阅读完成。

简略重述下重载与重写的区别:
  1. 重载。类的成员函数的函数名雷同,但参数列表不同(类型、个数、程序不同),就是函数的重载。C++ 编译器依据你调用函数时传入的不同的参数列表,去执行对应的那个函数。这点在 c 语言中是不能实现的,因为 C 语言没有这个语法个性,所以 C 编译器不反对,如果你这么写编译器必定会给你报错无奈编译通过。
  1. 重写。即笼罩,子类的某个成员函数 A 的返回值、函数名和参数列表和父类某个成员函数 A 截然不同,那么子类就把父类的 A 函数给笼罩掉了,当前调用子类的 A 函数,执行的是子类 A 函数的逻辑,如果在子类中不重写,则将会调用父类的 A 函数。

重载

简略剖析下,我猜想,重载在汇编中也没什么不同,必定和一般函数一个样,有不同的函数标号,执行哪个重载函数,编译器就会 Call 哪个函数。C++ 编译器会将每个成员函数都别离翻译成汇编并给它起个标号名,依据你要调用哪个具体的重载函数,去 Call 哪个汇编标号的函数。上面来剖析下:

#include<iostream>
/**
 * 继承:重载
*/

class Son{
public:
    int a;
    void add(){a = 0;}
    void add(int i){a = i;}
    void add(int i, int j){a = i+j;}

};

int main(){
    Son son;
    son.add();
    son.add(33);
    son.add(10, 78);
    return 0;
}

类中有 3 个重载函数 add,函数名一样,参数不一样,别离调用,看看编译成汇编是怎么实现重载个性的:

main:
.LFB1525:
    .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 # 调配 16 字节栈帧给 main 函数
    movq    %fs:40, %rax
    movq    %rax, -8(%rbp)
    xorl    %eax, %eax

    leaq    -12(%rbp), %rax # 算出 rbp-12 这个值
    movq    %rax, %rdi
    call    _ZN3Son3addEv # 调用 add()
    leaq    -12(%rbp), %rax
    movl    $33, %esi
    movq    %rax, %rdi
    call    _ZN3Son3addEi # 调用 add(int i)
    leaq    -12(%rbp), %rax
    movl    $78, %edx
    movl    $10, %esi
    movq    %rax, %rdi
    call    _ZN3Son3addEii # 调用 add(int i, int j)
    movl    $0, %eax
    movq    -8(%rbp), %rcx
    xorq    %fs:40, %rcx
    je    .L6
    call    __stack_chk_fail@PLT
.L6:
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

果然不出咱们所料,即便简单的重载概念,也只是编译器层面的抽象概念,真正体现到硬件、cpu 指令、内存调配与读写时,其实仍旧是 C 语言汇编那点事件,无非就是内存在哪调配,它的地址怎么计算,而后传参,Call 函数标号。通过这里,能够更加粗浅领会到,所谓 C ++ 的语法个性在汇编层面的实现,其实就是 C 语言汇编的各种组合,包含内存调配、地址计算、变量的字节长度计算、函数调用、参数传递等等。

当然,以上只是原理上并没有很绕的中央,但具体实现,要思考的细节很多,真正实现一个牢靠的编译器计算这些内存调配、地址计算等,要做大量工作,因而才会说古代 C ++ 编译器代码的复杂程度超过 linux 内核源码。

重写

间接上代码:

#include<iostream>
/**
 * 继承:重写
*/

class Father
{
public:
    int a;
    void add(){a = -1;}
};

class Son : public Father{
public:
    int s;
    void add(){a = 99;}
};


int main(){
    Father father;
    father.add();
    Son son;
    son.add();

    Father f = Son();
    f.add();
    return 0;
}

对应的汇编是:

main:
.LFB1524:
    .cfi_startproc
    endbr64
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $32, %rsp
    movq    %fs:40, %rax
    movq    %rax, -8(%rbp)
    xorl    %eax, %eax

    leaq    -24(%rbp), %rax
    movq    %rax, %rdi
    call    _ZN6Father3addEv # father.add()  调用的是父类的 add()

    leaq    -16(%rbp), %rax
    movq    %rax, %rdi
    call    _ZN3Son3addEv # son.add()  调用的是子类的 add()

    movl    $0, -20(%rbp)
    leaq    -20(%rbp), %rax
    movq    %rax, %rdi
    call    _ZN6Father3addEv # Father f = Son();  f.add();  调用的是父类的 add()


    movl    $0, %eax
    movq    -8(%rbp), %rdx
    xorq    %fs:40, %rdx
    je    .L5
    call    __stack_chk_fail@PLT
.L5:
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc

由此看出,重写,体现在汇编上,也只是 C 语言汇编中的函数调用和参数传递那一套流程,只不过,到底该调用谁,是 C ++ 编译器计算好了,也就是说,C++ 编译器把这种形象隐含的到底该调哪个函数的逻辑工作,曾经在编译时做完了,它给你的,也只是简略的汇编指令。

正文完
 0