C++是一种面向对象的高级语言,但是由于其基于C语言发展而来,因此其内在原理和C语言如出一辙,于是就来看看C++程序翻译成汇编代码是啥样的(在x86 linux环境下,使用g++ 翻译得到的结果),采用了C++11新标准,因此可以顺路看一下C++11中的mov语义究竟是如何实现的
一、翻译的原cpp文件:
extern double sqrt(double x);
extern double abs(double x);
struct Position
{
public:
Position(double px = 0 , double py = 0 , double pz = 0) : px(px) , py(py) , pz(pz){}
double px , py , pz;
};
int distance(const Position &x , const Position &y)
{
return abs(sqrt((x.px * x.px + x.py * x.py + x.pz * x.pz)) - sqrt((y.px * y.px + y.py * y.py + y.pz * y.pz)));
}
class Gun
{
public:
Gun(unsigned nbullet = 0) : nbullet(nbullet){}
virtual bool shot(const Position &pfrom , const Position &pto);
void set_bullet(unsigned n)
{
nbullet = n;
}
unsigned get_bullet()const
{
return nbullet;
}
virtual Gun* copy_self()const;
virtual ~Gun(){}
private:
unsigned nbullet;
};
Gun* Gun::copy_self()const
{
return new Gun(*this);
}
bool Gun::shot(const Position &pfrom , const Position &pto)
{
if(nbullet == 0)
return false;
else
nbullet--;
return true;
}
class HandGun : public Gun
{
public:
HandGun(unsigned nbullet = 0 , double dist = 0) : Gun(nbullet) , dist(dist){}
bool shot(const Position &px , const Position &py);
HandGun* copy_self()const;
void set_dist(double d)
{
dist = d;
}
double get_dist()const
{
return dist;
}
private:
double dist;
};
HandGun* HandGun::copy_self()const
{
return new HandGun(*this);
}
bool HandGun::shot(const Position &px , const Position &py)
{
if(!Gun::shot(px , py))
return false;
return distance(px , py) < dist;
}
class Soldior
{
public:
Soldior(const Position &pos , const Gun *g = nullptr) : pos(pos)
{
if(g == nullptr)
gun = nullptr;
else
gun = g->copy_self();
}
Soldior(const Soldior& s)
{
if(s.gun == nullptr)
gun = nullptr;
else
gun = s.gun->copy_self();
pos = s.pos;
}
Soldior(Soldior &&s) noexcept
{
gun = s.gun;
s.gun = nullptr;
pos = s.pos;
}
bool shot(const Position &pt)
{
return gun->shot(pos , pt);
}
Soldior& operator=(const Soldior &s)
{
if(this == &s)
return *this;
if(gun != nullptr)
delete gun;
if(s.gun == nullptr)
gun = nullptr;
else
gun = s.gun->copy_self();
pos = s.pos;
return *this;
}
Soldior& operator=(Soldior &&s)
{
if(this == &s)
return *this;
if(gun != nullptr)
delete gun;
gun = s.gun;
s.gun = nullptr;
pos = s.pos;
return *this;
}
~Soldior()
{
if(gun != nullptr)
delete gun;
}
private:
Gun *gun;
Position pos;
};
extern Soldior getSoldior();
int main(int argc , char *argv[])
{
Gun *g = new Gun(100);
Soldior *s = new Soldior(Position(20 , 30 , 50) , g);
*s = getSoldior();
delete g;
delete s;
}
二、翻译得到的对应asm汇编文件:(只保留了对我们理解C++有用的部分)
_ZN8PositionC2Eddd: ;constructor of Position
pushq %rbp
movq %rsp, %rbp ;save rbp
movq %rdi, -8(%rbp) ;get this pointer
movsd %xmm0, -16(%rbp) ;get px
movsd %xmm1, -24(%rbp) ;get py
movsd %xmm2, -32(%rbp) ;get pz
movq -8(%rbp), %rdx
movq -16(%rbp), %rax
movq %rax, (%rdx) ;init px
movq -8(%rbp), %rdx
movq -24(%rbp), %rax
movq %rax, 8(%rdx) ;init py
movq -8(%rbp), %rdx
movq -32(%rbp), %rax
movq %rax, 16(%rdx) ;init pz
popq %rbp ;recover rbp
ret
_Z8distanceRK8PositionS1_: ;distance
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp ;extend stack
movq %rdi, -8(%rbp) ;get address of x
movq %rsi, -16(%rbp) ;get address of y
movq -8(%rbp), %rax
movsd (%rax), %xmm1 ;get x.px
movq -8(%rbp), %rax
movsd (%rax), %xmm0 ;get x.px
mulsd %xmm0, %xmm1 ;%xmm1 = x.px * x.px
movq -8(%rbp), %rax
movsd 8(%rax), %xmm2
movq -8(%rbp), %rax
movsd 8(%rax), %xmm0
mulsd %xmm2, %xmm0 ;%xmm0 = x.py * x.py
addsd %xmm0, %xmm1 ;%xmm1 += %xmm0
movq -8(%rbp), %rax
movsd 16(%rax), %xmm2
movq -8(%rbp), %rax
movsd 16(%rax), %xmm0
mulsd %xmm2, %xmm0 ;%xmm0 = x.pz * x.pz
addsd %xmm1, %xmm0 ;%xmm0 += %xmm1
call _Z4sqrtd ;call sqrt with argument %xmm0
movsd %xmm0, -24(%rbp) ;save return value of sqrt
movq -16(%rbp), %rax ;mov address value of y into %rax
movsd (%rax), %xmm1
movq -16(%rbp), %rax
movsd (%rax), %xmm0
mulsd %xmm0, %xmm1 ;%xmm1 = y.px * y.px
movq -16(%rbp), %rax
movsd 8(%rax), %xmm2
movq -16(%rbp), %rax
movsd 8(%rax), %xmm0
mulsd %xmm2, %xmm0 ;%xmm0 = y.py * y.py
addsd %xmm0, %xmm1 ;%xmm1 += %xmm0
movq -16(%rbp), %rax
movsd 16(%rax), %xmm2
movq -16(%rbp), %rax
movsd 16(%rax), %xmm0
mulsd %xmm2, %xmm0 ;%xmm0 = y.pz * y.pz
addsd %xmm1, %xmm0 ;%xmm0 += %xmm1
call _Z4sqrtd ;call sqrt with argument %xmm0
movsd -24(%rbp), %xmm3 ;mov return value of first call to sqrt to %xmm3
subsd %xmm0, %xmm3 ;%xmm3 -= %xmm0
movapd %xmm3, %xmm0 ;save %xmm3 to %xmm0 for pass to call absd as argument
call _Z3absd ;call absd
cvttsd2si %xmm0, %eax
ret
_ZN3GunC2Ej: ;constructor of Gun
pushq %rbp
movq %rsp, %rbp
movq %rdi, -8(%rbp) ;get this pointer
movl %esi, -12(%rbp) ;get nbullet
movq -8(%rbp), %rax
movq $_ZTV3Gun+16, (%rax) ;pointer to virtual function table of class Gun
movq -8(%rbp), %rax
movl -12(%rbp), %edx
movl %edx, 8(%rax) ;init nbullet
popq %rbp
ret
_ZN3GunD2Ev: ;destructor of Gun
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp ;extend stack storage
movq %rdi, -8(%rbp) ;get this pointer
movq -8(%rbp), %rax
movq $_ZTV3Gun+16, (%rax) ;get pointer to virtual function table of class Gun
movl $0, %eax
testl %eax, %eax
je .L5
movq -8(%rbp), %rax
movq %rax, %rdi
call _ZdlPv
.L5:
leave
ret
_ZN3GunC2ERKS_: ;copy constructor of Gun
pushq %rbp
movq %rsp, %rbp
movq %rdi, -8(%rbp) ;get this pointer
movq %rsi, -16(%rbp) ;get address of argument gun
movq -8(%rbp), %rax
movq $_ZTV3Gun+16, (%rax) ;set pointer to virtual table with class Gun's virtual table address
movq -16(%rbp), %rax
movl 8(%rax), %edx ;%edx point to address of argument gun's nbullet
movq -8(%rbp), %rax
movl %edx, 8(%rax) ;init this->nbullet
popq %rbp
ret
_ZNK3Gun9copy_selfEv: ;Gun::copy_self
pushq %rbp
movq %rsp, %rbp
pushq %rbx
subq $24, %rsp ;extend stack
movq %rdi, -24(%rbp) ;get this pointer
movl $16, %edi ;mov size of Gun to %edi
call _Znwm ;alloc memory on heap
movq %rax, %rbx ;this pointer to new gun
movq -24(%rbp), %rax
movq %rax, %rsi ;my own this pointer
movq %rbx, %rdi ;this pointer to new gun
call _ZN3GunC1ERKS_ ;call copy constructor of Gun
movq %rbx, %rax ;set return value as this pointer to new gun
addq $24, %rsp ;resave stack
popq %rbx
popq %rbp
ret
_ZN3Gun4shotERK8PositionS2_: ;Gun::shot
pushq %rbp
movq %rsp, %rbp
movq %rdi, -8(%rbp) ;this pointer
movq %rsi, -16(%rbp) ;address of x
movq %rdx, -24(%rbp) ;address of y
movq -8(%rbp), %rax
movl 8(%rax), %eax ;address of this->nbullet
testl %eax, %eax
jne .L14 ;nbullet != 0
movl $0, %eax
jmp .L13
.L14:
movq -8(%rbp), %rax
movl 8(%rax), %eax ;address of this->nbullet
leal -1(%rax), %edx
movq -8(%rbp), %rax
movl %edx, 8(%rax)
.L13:
popq %rbp
ret
_ZN7HandGunC2Ejd: ;constructor of HandGun
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp
movq %rdi, -8(%rbp)
movl %esi, -12(%rbp)
movsd %xmm0, -24(%rbp)
movq -8(%rbp), %rax
movl -12(%rbp), %edx
movl %edx, %esi ;set nbullet
movq %rax, %rdi ;set this pointer
call _ZN3GunC2Ej ;call constructor of Gun
movq -8(%rbp), %rax
movq $_ZTV7HandGun+16, (%rax) ;set virtual function pointer
movq -8(%rbp), %rdx
movq -24(%rbp), %rax
movq %rax, 16(%rdx) ;init dist
leave
ret
_ZN7HandGunC2ERKS_: ;copy constructor of HandGun
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp)
movq %rsi, -16(%rbp)
movq -16(%rbp), %rdx
movq -8(%rbp), %rax
movq %rdx, %rsi ;set arg gun's this pointer
movq %rax, %rdi ;set my own this pointer
call _ZN3GunC2ERKS_ ;call Gun's copy constructor
movq -8(%rbp), %rax
movq $_ZTV7HandGun+16, (%rax) ;set virtual function pointer
movq -16(%rbp), %rax
movq 16(%rax), %rax ;get arg gun's dist
movq -8(%rbp), %rdx
movq %rax, 16(%rdx) ;init dist
leave
ret
_ZNK7HandGun9copy_selfEv: ;HandGun::copy_self
pushq %rbp
movq %rsp, %rbp
pushq %rbx
subq $24, %rsp
movq %rdi, -24(%rbp) ;my own this pointer
movl $24, %edi
call _Znwm
movq %rax, %rbx ;get address of new gun
movq -24(%rbp), %rax
movq %rax, %rsi ;set arg gun's address (my own this pointer)
movq %rbx, %rdi ;set this pointer (new gun's this pointer)
call _ZN7HandGunC1ERKS_ ;call copy constructor of HandGun
movq %rbx, %rax ;set new this pointer as return value
addq $24, %rsp ;resave stack
popq %rbx
popq %rbp
ret
_ZN7HandGun4shotERK8PositionS2_: ;HandGun::shot
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp
movq %rdi, -8(%rbp)
movq %rsi, -16(%rbp)
movq %rdx, -24(%rbp)
movq -8(%rbp), %rax ;this
movq -24(%rbp), %rdx ;addr of x
movq -16(%rbp), %rcx ;addr of y
movq %rcx, %rsi ;set y
movq %rax, %rdi ;set this
call _ZN3Gun4shotERK8PositionS2_ ;call Gun::shot
xorl $1, %eax ;check return value
testb %al, %al
je .L21 ;true
movl $0, %eax
jmp .L22
.L21:
movq -24(%rbp), %rdx
movq -16(%rbp), %rax
movq %rdx, %rsi ;set x
movq %rax, %rdi ;set y
call _Z8distanceRK8PositionS1_ ;call distance
cvtsi2sd %eax, %xmm0
movq -8(%rbp), %rax
movsd 16(%rax), %xmm1
ucomisd %xmm0, %xmm1
seta %al
.L22:
leave
ret
_ZN7SoldiorC2ERK8PositionPK3Gun: ;constructor of Soldior
pushq %rbp
movq %rsp, %rbp
subq $32, %rsp
movq %rdi, -8(%rbp)
movq %rsi, -16(%rbp)
movq %rdx, -24(%rbp)
movq -8(%rbp), %rax ;this
movq -16(%rbp), %rdx ;addr of pos
movq (%rdx), %rcx
movq %rcx, 8(%rax) ;init pos.px
movq 8(%rdx), %rcx
movq %rcx, 16(%rax) ;init pos.py
movq 16(%rdx), %rdx
movq %rdx, 24(%rax) ;init pos.pz
cmpq $0, -24(%rbp)
jne .L24 ;g != nullptr
movq -8(%rbp), %rax
movq $0, (%rax) ;init gun as nullptr
jmp .L23
.L24:
movq -24(%rbp), %rax ;this
movq (%rax), %rax ;pointer to Gun's virtual functions table
addq $8, %rax
movq (%rax), %rax ;second function pointer of g's virtual table
movq -24(%rbp), %rdx
movq %rdx, %rdi ;set g as 'this' argument
call *%rax ;call g->copy_self
movq -8(%rbp), %rdx
movq %rax, (%rdx) ;gun = g
.L23:
leave
ret
_ZN7SoldioraSEOS_: ;mov assignment
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp)
movq %rsi, -16(%rbp)
movq -8(%rbp), %rax
cmpq -16(%rbp), %rax
jne .L27 ;if(this != &s)
movq -8(%rbp), %rax
jmp .L28
.L27:
movq -8(%rbp), %rax
movq (%rax), %rax
testq %rax, %rax
je .L29 ;this->gun == nullptr
movq -8(%rbp), %rax
movq (%rax), %rax
testq %rax, %rax
je .L29
movq -8(%rbp), %rax
movq (%rax), %rax
movq (%rax), %rax
addq $24, %rax
movq (%rax), %rax
movq -8(%rbp), %rdx
movq (%rdx), %rdx
movq %rdx, %rdi
call *%rax ;call gun's destructor
.L29:
movq -16(%rbp), %rax
movq (%rax), %rdx
movq -8(%rbp), %rax
movq %rdx, (%rax) ;gun = s->gun
movq -16(%rbp), %rax
movq $0, (%rax) ;s->gun = nullptr
movq -8(%rbp), %rax
movq -16(%rbp), %rdx
movq 8(%rdx), %rcx
movq %rcx, 8(%rax) ;pos.px = s->pos.px
movq 16(%rdx), %rcx
movq %rcx, 16(%rax) ;pos.py = s->pos.py
movq 24(%rdx), %rdx
movq %rdx, 24(%rax) ;pos.pz = s->pos.pz
movq -8(%rbp), %rax ;return this
.L28:
leave
ret
_ZN7SoldiorD2Ev: ;destructor of Soldior
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movq (%rax), %rax ;%rax = gun
testq %rax, %rax
je .L30 ;gun == nullptr
movq -8(%rbp), %rax
movq (%rax), %rax
testq %rax, %rax
je .L30 ;gun == nullptr
movq -8(%rbp), %rax
movq (%rax), %rax
movq (%rax), %rax ;gun->vptr
addq $24, %rax ;third function pointer in gun's virtual table
movq (%rax), %rax
movq -8(%rbp), %rdx
movq (%rdx), %rdx ;get gun
movq %rdx, %rdi ;set gun as 'this' argument
call *%rax ;call gun's destructor
.L30:
leave
ret
main:
pushq %rbp
movq %rsp, %rbp
pushq %r12
pushq %rbx
subq $80, %rsp
movl %edi, -68(%rbp)
movq %rsi, -80(%rbp)
movl $16, %edi ;set alloc size as Gun
call _Znwm ;alloc memory on heap
movq %rax, %rbx ;%rbx is this pointer of new gun
movl $100, %esi ;set nbullet
movq %rbx, %rdi ;set 'this' arg
call _ZN3GunC1Ej ;call constructor of Gun
movq %rbx, -64(%rbp) ;save %rbx
movabsq $4632233691727265792, %rcx ;constexpr accessed as static address
movabsq $4629137466983448576, %rdx
movabsq $4626322717216342016, %rax
leaq -48(%rbp), %rsi
movq %rcx, -88(%rbp)
movsd -88(%rbp), %xmm2 ;px
movq %rdx, -88(%rbp)
movsd -88(%rbp), %xmm1 ;py
movq %rax, -88(%rbp)
movsd -88(%rbp), %xmm0 ;pz
movq %rsi, %rdi ;set 'this' arg
call _ZN8PositionC1Eddd ;call constructor of Position
leaq -48(%rbp), %r12
movl $32, %edi ;set alloc size as Soldior's size
call _Znwm
movq %rax, %rbx ;s
movq -64(%rbp), %rax
movq %rax, %rdx ;set g
movq %r12, %rsi ;set pos
movq %rbx, %rdi ;set 'this' arg
call _ZN7SoldiorC1ERK8PositionPK3Gun ;call Soldior's constructor
movq %rbx, -56(%rbp) ;save %rbx
leaq -48(%rbp), %rax
movq %rax, %rdi ;set return addr of next function
call _Z10getSoldiorv ;call getSoldior
leaq -48(%rbp), %rdx ;get return val
movq -56(%rbp), %rax ;get s
movq %rdx, %rsi ;set 's' arg
movq %rax, %rdi ;set 'this' arg
call _ZN7SoldioraSEOS_ ;call mov assignment of Soldior
leaq -48(%rbp), %rax
movq %rax, %rdi
call _ZN7SoldiorD1Ev ;delete return val of getSoldior
movl $0, %eax
jmp .L38
.L37:
movq %rax, %r12
movq %rbx, %rdi
call _ZdlPv
movq %r12, %rax
movq %rax, %rdi
call _Unwind_Resume
.L38:
addq $80, %rsp
popq %rbx
popq %r12
popq %rbp
ret
_ZTV3Gun: ;Gun's virtual functions table
.quad 0
.quad _ZTI3Gun ;some information of this table , we can ignore
.quad _ZN3Gun4shotERK8PositionS2_
.quad _ZNK3Gun9copy_selfEv
.quad _ZN3GunD1Ev
.quad _ZN3GunD0Ev
.weak _ZTI7HandGun
.section .rodata._ZTI7HandGun,"aG",@progbits,_ZTI7HandGun,comdat
.align 16
.type _ZTI7HandGun, @object
.size _ZTI7HandGun, 24
_ZTV7HandGun: ;virtual function table of HandGun
.quad 0
.quad _ZTI7HandGun
.quad _ZN7HandGun4shotERK8PositionS2_
.quad _ZNK7HandGun9copy_selfEv
.quad _ZN7HandGunD1Ev
.quad _ZN7HandGunD0Ev
.section .text._ZN7HandGunD2Ev,"axG",@progbits,_ZN7HandGunD5Ev,comdat
.align 2
.weak _ZN7HandGunD2Ev
.type _ZN7HandGunD2Ev, @function
_ZN7HandGunD2Ev: ;destructor of HandGun
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movq $_ZTV7HandGun+16, (%rax) ;set vptr
movq -8(%rbp), %rax
movq %rax, %rdi
call _ZN3GunD2Ev ;call Gun's destructor
movl $0, %eax
testl %eax, %eax
je .L39
movq -8(%rbp), %rax
movq %rax, %rdi
call _ZdlPv
.L39:
leave
ret
由此总结一下C++中虚函数的实现机制, 首先,C++中的每个定义了虚函数的类都实现了一个虚函数表,虚函数表中存储了这个类中每个虚函数的实际地址,C++为每个定义了虚函数的类的对象插入了一个隐式的成员:虚函数表指针,指向这个类对应的虚函数表。那么这个虚函数表指针是什么时候初始化的呢?答案是:在一个类的构造函数或析构函数执行的第一步就进行虚函数指针的初始化,这样就是实现了在构造函数和析构函数中调用虚函数始终是自己本身定义的函数。比如,一个子类对象开始构造,先执行父类的构造函数,父类的构造函数第一步先将虚函数指针指向了父类的虚函数表,从而在父类的构造函数中调用虚函数始终调用的是自己定义的虚函数,父类的构造函数执行完后执行子类的构造函数,子类的构造函数现将虚函数指针指向它本身所属类的虚函数表,此后直到子类的析构函数执行,都未在任何地方修改这个虚函数指针,从而在此期间任何对虚函数的调用都执行的是子类自己的虚函数。最后,该对象要被析构,首先调用子类的析构函数,子类的析构函数首先将他的虚函数指针指向他自己所属类的虚函数表(其实如果这个子类没有子类的话,这一步并未改变子类虚函数指针的指向,但是计算机没有人类那么聪明,所以这一步必须执行)。执行完子类的析构函数后调用父类的析构函数,父类的析构函数首先将虚函数指针指向父类的虚函数表,从而父类的构造函数中调用的永远是他自己定义的虚函数,父类的析构函数执行完毕后整个对象析构结束。