【学习记录】关于c++类和多态的底层原理探索

示例代码如下:

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的偏移不一样)。

由此看来,虽然上层可以搞得花里胡哨,但是对于底层来说众生平等,任你什么对象继承多态的,对于汇编层面就是库库掉函数,库库写寄存器,所以上层对于程序员的轻松,只是有人替我们负重前行了(编译器)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值