示例代码如下:
class Base {
public:
int B_int;
void add() {
B_int++;
}
virtual void hello(){
B_int+=2;
}
};
class Derived : public Base {
public:
int D_int;
void sub() {
D_int--;
}
void hello() override {
D_int+=2;
}
};
int main() {
Derived d;
Base c;
d.D_int=0;
c.B_int=0;
d.add();
c.add();
d.sub();
}
生成汇编代码如下:
.arch armv8-a
.file "main.cpp"
.text
.section .text._ZN4Base3addEv,"axG",@progbits,_ZN4Base3addEv,comdat
.align 2
.weak _ZN4Base3addEv
.type _ZN4Base3addEv, %function
_ZN4Base3addEv:
.LFB0:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
str x0, [sp, 8]
ldr x0, [sp, 8]
ldr w0, [x0, 8]
add w1, w0, 1
ldr x0, [sp, 8]
str w1, [x0, 8]
nop
add sp, sp, 16
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE0:
.size _ZN4Base3addEv, .-_ZN4Base3addEv
.section .text._ZN4Base5helloEv,"axG",@progbits,_ZN4Base5helloEv,comdat
.align 2
.weak _ZN4Base5helloEv
.type _ZN4Base5helloEv, %function
_ZN4Base5helloEv:
.LFB1:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
str x0, [sp, 8]
ldr x0, [sp, 8]
ldr w0, [x0, 8]
add w1, w0, 2
ldr x0, [sp, 8]
str w1, [x0, 8]
nop
add sp, sp, 16
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE1:
.size _ZN4Base5helloEv, .-_ZN4Base5helloEv
.section .text._ZN7Derived3subEv,"axG",@progbits,_ZN7Derived3subEv,comdat
.align 2
.weak _ZN7Derived3subEv
.type _ZN7Derived3subEv, %function
_ZN7Derived3subEv:
.LFB2:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
str x0, [sp, 8]
ldr x0, [sp, 8]
ldr w0, [x0, 12]
sub w1, w0, #1
ldr x0, [sp, 8]
str w1, [x0, 12]
nop
add sp, sp, 16
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE2:
.size _ZN7Derived3subEv, .-_ZN7Derived3subEv
.section .text._ZN7Derived5helloEv,"axG",@progbits,_ZN7Derived5helloEv,comdat
.align 2
.weak _ZN7Derived5helloEv
.type _ZN7Derived5helloEv, %function
_ZN7Derived5helloEv:
.LFB3:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
str x0, [sp, 8]
ldr x0, [sp, 8]
ldr w0, [x0, 12]
add w1, w0, 2
ldr x0, [sp, 8]
str w1, [x0, 12]
nop
add sp, sp, 16
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE3:
.size _ZN7Derived5helloEv, .-_ZN7Derived5helloEv
.section .text._ZN4BaseC2Ev,"axG",@progbits,_ZN4BaseC5Ev,comdat
.align 2
.weak _ZN4BaseC2Ev
.type _ZN4BaseC2Ev, %function
_ZN4BaseC2Ev:
.LFB7:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
str x0, [sp, 8]
adrp x0, _ZTV4Base+16
add x1, x0, :lo12:_ZTV4Base+16
ldr x0, [sp, 8]
str x1, [x0]
nop
add sp, sp, 16
.cfi_def_cfa_offset 0
ret
.cfi_endproc
.LFE7:
.size _ZN4BaseC2Ev, .-_ZN4BaseC2Ev
.weak _ZN4BaseC1Ev
.set _ZN4BaseC1Ev,_ZN4BaseC2Ev
.section .text._ZN7DerivedC2Ev,"axG",@progbits,_ZN7DerivedC5Ev,comdat
.align 2
.weak _ZN7DerivedC2Ev
.type _ZN7DerivedC2Ev, %function
_ZN7DerivedC2Ev:
.LFB9:
.cfi_startproc
stp x29, x30, [sp, -32]!
.cfi_def_cfa_offset 32
.cfi_offset 29, -32
.cfi_offset 30, -24
add x29, sp, 0
.cfi_def_cfa_register 29
str x0, [x29, 24]
ldr x0, [x29, 24]
bl _ZN4BaseC2Ev
adrp x0, _ZTV7Derived+16
add x1, x0, :lo12:_ZTV7Derived+16
ldr x0, [x29, 24]
str x1, [x0]
nop
ldp x29, x30, [sp], 32
.cfi_restore 30
.cfi_restore 29
.cfi_def_cfa 31, 0
ret
.cfi_endproc
.LFE9:
.size _ZN7DerivedC2Ev, .-_ZN7DerivedC2Ev
.weak _ZN7DerivedC1Ev
.set _ZN7DerivedC1Ev,_ZN7DerivedC2Ev
.text
.align 2
.global main
.type main, %function
main:
.LFB4:
.cfi_startproc
stp x29, x30, [sp, -64]!
.cfi_def_cfa_offset 64
.cfi_offset 29, -64
.cfi_offset 30, -56
add x29, sp, 0
.cfi_def_cfa_register 29
adrp x0, :got:__stack_chk_guard
ldr x0, [x0, #:got_lo12:__stack_chk_guard]
ldr x1, [x0]
str x1, [x29, 56]
mov x1,0
add x0, x29, 24
bl _ZN7DerivedC1Ev
add x0, x29, 40
bl _ZN4BaseC1Ev
str wzr, [x29, 36]
str wzr, [x29, 48]
add x0, x29, 24
bl _ZN4Base3addEv
add x0, x29, 40
bl _ZN4Base3addEv
add x0, x29, 24
bl _ZN7Derived3subEv
mov w0, 0
adrp x1, :got:__stack_chk_guard
ldr x1, [x1, #:got_lo12:__stack_chk_guard]
ldr x2, [x29, 56]
ldr x1, [x1]
eor x1, x2, x1
cmp x1, 0
beq .L9
bl __stack_chk_fail
.L9:
ldp x29, x30, [sp], 64
.cfi_restore 30
.cfi_restore 29
.cfi_def_cfa 31, 0
ret
.cfi_endproc
.LFE4:
.size main, .-main
.weak _ZTV7Derived
.section .data.rel.ro.local._ZTV7Derived,"awG",@progbits,_ZTV7Derived,comdat
.align 3
.type _ZTV7Derived, %object
.size _ZTV7Derived, 24
_ZTV7Derived:
.xword 0
.xword _ZTI7Derived
.xword _ZN7Derived5helloEv
.weak _ZTV4Base
.section .data.rel.ro.local._ZTV4Base,"awG",@progbits,_ZTV4Base,comdat
.align 3
.type _ZTV4Base, %object
.size _ZTV4Base, 24
_ZTV4Base:
.xword 0
.xword _ZTI4Base
.xword _ZN4Base5helloEv
.weak _ZTI7Derived
.section .data.rel.ro._ZTI7Derived,"awG",@progbits,_ZTI7Derived,comdat
.align 3
.type _ZTI7Derived, %object
.size _ZTI7Derived, 24
_ZTI7Derived:
.xword _ZTVN10__cxxabiv120__si_class_type_infoE+16
.xword _ZTS7Derived
.xword _ZTI4Base
.weak _ZTS7Derived
.section .rodata._ZTS7Derived,"aG",@progbits,_ZTS7Derived,comdat
.align 3
.type _ZTS7Derived, %object
.size _ZTS7Derived, 9
_ZTS7Derived:
.string "7Derived"
.weak _ZTI4Base
.section .data.rel.ro._ZTI4Base,"awG",@progbits,_ZTI4Base,comdat
.align 3
.type _ZTI4Base, %object
.size _ZTI4Base, 16
_ZTI4Base:
.xword _ZTVN10__cxxabiv117__class_type_infoE+16
.xword _ZTS4Base
.weak _ZTS4Base
.section .rodata._ZTS4Base,"aG",@progbits,_ZTS4Base,comdat
.align 3
.type _ZTS4Base, %object
.size _ZTS4Base, 6
_ZTS4Base:
.string "4Base"
.ident "GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
g++ -S -o hello.s main.cpp //汇编
首先对c++代码进行说明,基类Base有方法add 和 hello 派生类Derived有继承自Base 有方法add sub和重写过的hello
通过汇编查看
_ZN4Base3addEv: ;此段为Base类的add方法,而Derived并没有该方法
.LFB0:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
str x0, [sp, 8]
ldr x0, [sp, 8]
ldr w0, [x0, 8]
add w1, w0, 1
ldr x0, [sp, 8]
str w1, [x0, 8]
nop
add sp, sp, 16
.cfi_def_cfa_offset 0
ret
在main中查看:发现是调用了两次Base的add方法通过对寄存器x0设置不同的栈内地址实现的不同参数传递而已
...
str wzr, [x29, 36]
str wzr, [x29, 48]
add x0, x29, 24
bl _ZN4Base3addEv
add x0, x29, 40
bl _ZN4Base3addEv
add x0, x29, 24
bl _ZN7Derived3subEv
mov w0, 0
...
这里提一嘴:当前架构和编译器下:参数传递较少的时候使用的是寄存器传值,通常来说当参数大于四个的时候就会使用堆栈传值,并且在类方法调用时,所有的非静态成员函数都隐式的接收了一个参数为类对象本身,可以理解为this,在类方法内部如果想要访问内对象中的值则通过地址偏移访问,形式上相当于c中的
void add(void * this){
*((int *)(this+(偏移地址))).B_int++;
}
只不过这些内容c++编译器帮我们做了而已,如此使用c来实现c++的面向对象多态等功能是完全可行的,只不过我们需要手动补齐以上编译器帮我们做的这些内容。以下举个例子来抛砖引玉
typedef struct{
int B_int;
void (*Base)(void *self);
void (*add)(void *self);
}Base;
void add(void *self){
Base * this=(Base *)self;
this->B_int++;
}
int main(){
Base base1; //对象实例化
base1.Base(&base1); //这里模仿的是构造函数的调用,c++中不需要我们这样写而是在实例化对象的时候自动调用,C语言就需要手动补上
//此外c++编译器会自动在我们调取函数时将base1.add等直接跳转到对应的地址如之前的汇编_ZN4Base3addEv,在这里我们手写面向对象
//则需要我们自己做这部分内容,所以我们要在构造函数中补上函数指针的赋值 对于以上就是this->add=add;其它所有方法都是如此
base1.add(&base1);//这里就是实际的调用对象的方法,编译器帮我们传递的默认参数需要我们补上也就是对象本身
}
然后时虚函数,虚函数在子类中可以被子类重写,但是观察生成的汇编代码
_ZN4Base5helloEv:
.LFB1:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
str x0, [sp, 8]
ldr x0, [sp, 8]
ldr w0, [x0, 8]
add w1, w0, 2
ldr x0, [sp, 8]
str w1, [x0, 8]
nop
add sp, sp, 16
.cfi_def_cfa_offset 0
ret
_ZN7Derived5helloEv:
.LFB3:
.cfi_startproc
sub sp, sp, #16
.cfi_def_cfa_offset 16
str x0, [sp, 8]
ldr x0, [sp, 8]
ldr w0, [x0, 12]
add w1, w0, 2
ldr x0, [sp, 8]
str w1, [x0, 12]
nop
add sp, sp, 16
.cfi_def_cfa_offset 0
ret
可以发现尽管功能一幕一样,但是编译器还是帮我们生成了一份一样的代码(上图中的差异是内存偏移不一样,就是d->D_int和c->B_int的偏移不一样)。
由此看来,虽然上层可以搞得花里胡哨,但是对于底层来说众生平等,任你什么对象继承多态的,对于汇编层面就是库库掉函数,库库写寄存器,所以上层对于程序员的轻松,只是有人替我们负重前行了(编译器)。