用汇编的眼光看C++(之class构造、析构) ,(之拷贝、赋值函数)
来源:互联网 发布:河南人 知乎 编辑:程序博客网 时间:2024/04/30 02:17
用汇编的眼光看C++(之class构造、析构)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
前面我们讨论基本上都是C语言的内容,还没有真正触及到C++的相关知识。从这篇博客之后,我们将会更多触及类的内容。类的属性很多,今天我们讨论主要就是构造函数、析构函数。
(1)如果没有构造函数、析构函数呢?
- class apple
- {
- public:
- void print() const {return;}
- };
class apple{public:void print() const {return;}};
虽然这个类没有什么意义,但是如果用sizeof计算一下大小的话,我们发现它还是占了一个字节。那么如果有一个apple的变量呢?为了让apple的变量有意义,我们尝试做一个改变:
- 66: apple a;
- 67: a.print();
- 00401248 lea ecx,[ebp-4]
- 0040124B call @ILT+0(apple::print) (00401005)
- 68: return;
- 69: }
66: apple a;67: a.print();00401248 lea ecx,[ebp-4]0040124B call @ILT+0(apple::print) (00401005)68: return;69: }
我们看到,堆栈分配了四个字节空间给a,就是ebp下面的一个字节。
(2) 析构函数什么时候调用?
- class apple
- {
- public:
- apple() {printf("apple()!\n");}
- ~apple() {printf("~apple()!\n");}
- void print() const {return;}
- };
class apple{public:apple() {printf("apple()!\n");}~apple() {printf("~apple()!\n");}void print() const {return;}};
如果调用呢,我们可以做一个测试环境,如下所示:
- 68: apple a;
- 0040126D lea ecx,[ebp-10h]
- 00401270 call @ILT+65(apple::apple) (00401046)
- 00401275 mov dword ptr [ebp-4],0
- 69: {
- 70: apple b;
- 0040127C lea ecx,[b]
- 0040127F call @ILT+65(apple::apple) (00401046)
- 71: }
- 00401284 lea ecx,[b]
- 00401287 call @ILT+0(apple::~apple) (00401005)
- 0040128C mov dword ptr [ebp-4],0FFFFFFFFh
- 72: }
- 00401293 lea ecx,[ebp-10h]
- 00401296 call @ILT+0(apple::~apple) (00401005)
- 0040129B mov ecx,dword ptr [ebp-0Ch]
- 0040129E mov dword ptr fs:[0],ecx
68: apple a;0040126D lea ecx,[ebp-10h]00401270 call @ILT+65(apple::apple) (00401046)00401275 mov dword ptr [ebp-4],069: {70: apple b;0040127C lea ecx,[b]0040127F call @ILT+65(apple::apple) (00401046)71: }00401284 lea ecx,[b]00401287 call @ILT+0(apple::~apple) (00401005)0040128C mov dword ptr [ebp-4],0FFFFFFFFh72: }00401293 lea ecx,[ebp-10h]00401296 call @ILT+0(apple::~apple) (00401005)0040129B mov ecx,dword ptr [ebp-0Ch]0040129E mov dword ptr fs:[0],ecx
我们看到,只要出了作用域,析构函数就会自动会被调用。
(3)如果是new调用类,析构函数会自动调用吗?
不会。
(4)构造函数、析构函数的本质?
我们知道在函数中的临时变量在堆栈里面应用的时候都需要初始化处理的,在堆栈返回的时候会被自动收回。那么构造函数和析构函数?其实是一样的,在函数调用的时候,堆栈也会为这样一个类准备大小合适的堆栈,然后调用构造函数对这样的一片内存进行初始化处理,在函数return的时候,调用另外一个函数对可能涉及到的资源进行一次清理。这里指的资源不是指内存空间,而是指广义意义上的系统资源、比如说IO、socket、锁、画笔、对话框句柄等等。所以,通常而言,如果你在析构函数里面没有及时对资源进行分配,那么就会造成资源的泄露。这一切只有等到程序结束的时候才会重新回到系统的手中,不过如果一个程序占有太多的资源,那么势必会对别的程序造成影响。
用汇编的眼光看C++(之拷贝、赋值函数)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】
拷贝构造函数和复制函数是类里面比较重要的两个函数。两者有什么区别呢?其实也很简单,我们可以举个例子,加入有这样一个类的定义:
- class apple
- {
- public:
- apple() { printf("apple()!\n");}
- apple(apple& a) { printf("copy apple()!\n");}
- apple& operator=(apple& a) { printf("= apple()\n"); return *this;}
- ~apple() { printf("~apple()!\n");}
- void print() const { return;}
- };
class apple{public:apple() { printf("apple()!\n");}apple(apple& a) { printf("copy apple()!\n");}apple& operator=(apple& a) { printf("= apple()\n"); return *this;}~apple() { printf("~apple()!\n");}void print() const { return;}};
那么我们在如下的函数里面进行调用的时候,调用的函数分别是哪些呢?
- void process()
- {
- apple a, c;
- apple b =a;
- c = b;
- }
void process(){apple a, c;apple b =a;c = b;}
其实汇编的结果是这样的,大家可以一起看一下,自己尝试读一下。如果一次不是很明白,可以多读几次。
- 70: apple a, c;
- 0040127D lea ecx,[ebp-10h]
- 00401280 call @ILT+70(apple::apple) (0040104b)
- 00401285 mov dword ptr [ebp-4],0
- 0040128C lea ecx,[ebp-14h]
- 0040128F call @ILT+70(apple::apple) (0040104b)
- 00401294 mov byte ptr [ebp-4],1
- 71: apple b =a;
- 00401298 lea eax,[ebp-10h]
- 0040129B push eax
- 0040129C lea ecx,[ebp-18h]
- 0040129F call @ILT+50(apple::apple) (00401037)
- 004012A4 mov byte ptr [ebp-4],2
- 72: c = b;
- 004012A8 lea ecx,[ebp-18h]
- 004012AB push ecx
- 004012AC lea ecx,[ebp-14h]
- 004012AF call @ILT+75(apple::operator=) (00401050)
- 73: }
- 004012B4 mov byte ptr [ebp-4],1
- 004012B8 lea ecx,[ebp-18h]
- 004012BB call @ILT+0(apple::~apple) (00401005)
- 004012C0 mov byte ptr [ebp-4],0
- 004012C4 lea ecx,[ebp-14h]
- 004012C7 call @ILT+0(apple::~apple) (00401005)
- 004012CC mov dword ptr [ebp-4],0FFFFFFFFh
- 004012D3 lea ecx,[ebp-10h]
- 004012D6 call @ILT+0(apple::~apple) (00401005)
- 004012DB mov ecx,dword ptr [ebp-0Ch]
- 004012DE mov dword ptr fs:[0],ecx
- 004012E5 pop edi
- 004012E6 pop esi
- 004012E7 pop ebx
- 004012E8 add esp,58h
- 004012EB cmp ebp,esp
- 004012ED call __chkesp (004087c0)
- 004012F2 mov esp,ebp
- 004012F4 pop ebp
- 004012F5 ret
70: apple a, c;0040127D lea ecx,[ebp-10h]00401280 call @ILT+70(apple::apple) (0040104b)00401285 mov dword ptr [ebp-4],00040128C lea ecx,[ebp-14h]0040128F call @ILT+70(apple::apple) (0040104b)00401294 mov byte ptr [ebp-4],171: apple b =a;00401298 lea eax,[ebp-10h]0040129B push eax0040129C lea ecx,[ebp-18h]0040129F call @ILT+50(apple::apple) (00401037)004012A4 mov byte ptr [ebp-4],272: c = b;004012A8 lea ecx,[ebp-18h]004012AB push ecx004012AC lea ecx,[ebp-14h]004012AF call @ILT+75(apple::operator=) (00401050)73: }004012B4 mov byte ptr [ebp-4],1004012B8 lea ecx,[ebp-18h]004012BB call @ILT+0(apple::~apple) (00401005)004012C0 mov byte ptr [ebp-4],0004012C4 lea ecx,[ebp-14h]004012C7 call @ILT+0(apple::~apple) (00401005)004012CC mov dword ptr [ebp-4],0FFFFFFFFh004012D3 lea ecx,[ebp-10h]004012D6 call @ILT+0(apple::~apple) (00401005)004012DB mov ecx,dword ptr [ebp-0Ch]004012DE mov dword ptr fs:[0],ecx004012E5 pop edi004012E6 pop esi004012E7 pop ebx004012E8 add esp,58h004012EB cmp ebp,esp004012ED call __chkesp (004087c0)004012F2 mov esp,ebp004012F4 pop ebp004012F5 ret
代码有点长,大家可以一句一句来看,比如说就按照70、71、72、73分别查看对应的汇编代码:
(1)70句: 我们看到函数做了两次函数调用,恰好就是apple的构造函数调用。这也正好对应着两个临时变量a和c,两个变量的地址分别是【ebp-10】和【ebp-14】,这里也可以看出整个类的大小就是4个字节,就是一块存放数据的普通内存。而构造函数之所以能和对应的内存绑定在一起,主要是因为ecx记录了内存的起始地址,这在C++编译中是十分关键的。我们看到的C++构造函数好像是没有绑定内存,实际上在VC里面已经做好了约定,ecx就是this指针,就是类的内存起始地址。有兴趣的同学看看G++编译的时候,采用的this指针是哪个寄存器保存的?(其实是eax)
(2)71句:通过对应看到了eax记录了引用变量的地址,而ecx是ebp下面紧挨着四个字节。但是函数调用的地址和前面的缺省构造函数不太一样,所以我们大胆猜测,这里的构造函数这是拷贝构造函数,我们可以在调试的时候查看一下打印消息。
(3)72句:0x4012AF语句已经清楚地告诉了我们,这里调用的函数就是operator=函数,这一部分是算术符重载的内容,我们在后面的博客会重点介绍。
(4)73句: 前面我们讲过,析构函数在函数调用结束的时候被被自动调用,那么这里我们看到却是出现了三个调用?这三个变量正好是我们之前说的a、b、c三个变量。那么这三个变量调用的次序是怎样的呢?我们可以查看一下变量的地址,分别是【ebp-18h】、【ebp-14h】、【ebp-10h】,这正好和变量出现的顺序相反。所以我们看到,析构函数和构造函数是严格一一对应的,谁先出现,谁后析构。
- 用汇编的眼光看C++(之class构造、析构) ,(之拷贝、赋值函数)
- 用汇编的眼光看C++(之拷贝、赋值函数)
- 用汇编的眼光看C++(之拷贝、赋值函数)
- 用汇编的眼光看C++(之拷贝、赋值函数)
- 用汇编的眼光看C++(之拷贝、赋值函数)
- 用汇编的眼光看C++(之拷贝、赋值函数)
- 用汇编的眼光看C++(之拷贝、赋值函数)
- 用汇编的眼光看C++(之class构造、析构)
- 用汇编的眼光看C++(之class构造、析构)
- 用汇编的眼光看C++(之class构造、析构)
- 用汇编的眼光看C++(之class构造、析构)
- 用汇编的眼光看C++(之class构造、析构)
- 从汇编的眼光看C++(之指针拷贝)
- 从汇编的眼光看C++(之指针拷贝)
- 从汇编的眼光看C++(之指针拷贝)
- 用汇编的眼光看C++(之特殊函数)
- 用汇编的眼光看C++(之虚函数)
- 用汇编的眼光看c++(之模板函数)
- hduoj2042,不容易系列之二,递推水题
- linux 进程(关于守护进程、检查一个进程是否活着、如何写一个进程号文件)
- linux sed
- USACO 3.2 Stringsobits
- JavaScript面试题
- 用汇编的眼光看C++(之class构造、析构) ,(之拷贝、赋值函数)
- ok6410-第06篇-裸机led程序
- java itext html转pdf[续篇]
- linux awk
- UML ----用例图
- C#上机 第七周 任务2 判断该物体是否会在水中下沉
- Struts2基础之八:action的缺省配置和包含其他配置文件
- 用汇编的眼光看C++(之类继承) ,(之虚函数) .
- hduoj2045,不容易系列之(3)—— LELE的RPG难题,递推水题