反汇编(三)C/C++ 结构体与类(1)--this指针、类成员与类成员函数

类的内容有点多,分章节研究

结构体跟类

1.结构体跟类

结构体跟类本质上没有太大区别,只是结构体成员默认为public的,而类则是private。这里直接省去了结构体的反汇编。直接看类。

#include<iostream>
using namespace std;

class A
{
public:	
	/*
	 	以下数据成员探讨 一个类的大小由什么决定 
	 	以及数据排列 内存对齐等特性 
	*/ 
	const static int sos = 654;	
	int old;
	int old2;
	char ui;
	char po[14];
	
	/*
		以下来探讨函数在类中是如何表现的  
	*/ 
	
	int getNumber()
	{
		return old + old2;
	}
	//这个函数来看看this指针到底是什么 
	void seeThis()
	{
		this->old = 555;
	}
	
};

int main()
{
	A a;
	a.getNumber();
	a.seeThis();
	
	
	A b;
	b.getNumber();
	b.seeThis(); 
	
	
	return 0;
} 

选择了静态反汇编工具IDAPro跟动态反汇编OD(便于调试),配合进行 。附上了反汇编代码

main函数部分:

这里并没有发现实例对象的代码.(构造函数的问题?这里先放放),看其他部分

.text:004013B0 ; int main()
.text:004013B0                 public _main
.text:004013B0 _main           proc near               ; CODE XREF: ___mingw_CRTStartup+F8p
.text:004013B0                 push    ebp
.text:004013B1                 mov     ebp, esp
.text:004013B3                 and     esp, 0FFFFFFF0h
.text:004013B6                 sub     esp, 30h
.text:004013B9                 call    ___main

                               //这里开始
                               //把a的首地址赋值给eax,然后赋值给ecx
                               
.text:004013BE                 lea     eax, [esp+18h]
.text:004013C2                 mov     ecx, eax

                               //调用a的成员函数getNumber()

.text:004013C4                 call    __ZN1A9getNumberEv ; A::getNumber(void)

                               //把a的首地址赋值给eax,然后赋值给ecx

.text:004013C9                 lea     eax, [esp+18h]
.text:004013CD                 mov     ecx, eax
                               //调用a的成员函数seeThis()
.text:004013CF                 call    __ZN1A7seeThisEv ; A::seeThis(void)

                               //把b的首地址赋值给eax,然后赋值给ecx

.text:004013D4                 mov     eax, esp
.text:004013D6                 mov     ecx, eax
                               //调用b的getNumber()
.text:004013D8                 call    __ZN1A9getNumberEv ; A::getNumber(void)
                               
                               //把b的首地址放到ecx中
.text:004013DD                 mov     eax, esp
.text:004013DF                 mov     ecx, eax
                               //调用b的seeThis()函数
.text:004013E1                 call    __ZN1A7seeThisEv ; A::seeThis(void)
.text:004013E6                 mov     eax, 0
.text:004013EB                 leave
.text:004013EC                 retn
.text:004013EC _main           endp

这里产生的问题:

1.类的在内存中的排列由什么决定?

看到  .text:004013BE 这行 与 .text:004013D4 这行。esp栈顶寄存器相隔18h,也就是24字节大小。反观类A,两个int类型,

一个char型,一个14字节的char数组,一个静态成员。除去静态成员4字节的大小(静态数据成员不算大小),一个char 加上 char数组 (这里有内存对齐,对齐的部分留空),一共算16字节,两个int 8字节,相加为24字节。

这里可以初步总结:不算内存对齐的留空内存,类在内存中的排列方式大致跟数组相同。静态数据成员其实跟全局变量相同。只不过编译器做了作用域限制。这里不再详细给出,但是可以肯定是,静态数据成员的位置不在栈空间中。那么类在内存中的排列其实还是看编译器怎么处理,内存对齐也复杂,这里以看到的为准,就轻描淡写的过了。

2.在调用成员函数时,为什么要把a,b的首地址放到ecx寄存器中?

在看一段反汇编代码:

.text:0041B6B0 ; void __cdecl A::seeThis(A *const this)
.text:0041B6B0 __ZN1A7seeThisEv proc near              ; CODE XREF: _main+1Fp
.text:0041B6B0                                         ; _main+31p
.text:0041B6B0
.text:0041B6B0 this            = dword ptr -4
.text:0041B6B0
.text:0041B6B0                 push    ebp
.text:0041B6B1                 mov     ebp, esp
.text:0041B6B3                 sub     esp, 4
                            
                               //ecx即为this   
                            
.text:0041B6B6                 mov     [ebp+this], ecx
                               //this放入eax
.text:0041B6B9                 mov     eax, [ebp+this]、
                               //this->old = 555;
.text:0041B6BC                 mov     dword ptr [eax], 22Bh
.text:0041B6C2                 leave
.text:0041B6C3                 retn
.text:0041B6C3 __ZN1A7seeThisEv endp
.text:0041B6C3
.text:0041B6C4
.text:0041B6C4 ; =============== S U B R O U T I N E =======================================
.text:0041B6C4
.text:0041B6C4 ; Attributes: static bp-based frame
.text:0041B6C4
.text:0041B6C4 ; int __cdecl A::getNumber(A *const this)
.text:0041B6C4                 public __ZN1A9getNumberEv
.text:0041B6C4 __ZN1A9getNumberEv proc near            ; CODE XREF: _main+14p
.text:0041B6C4                                         ; _main+28p
.text:0041B6C4
.text:0041B6C4 this            = dword ptr -4
.text:0041B6C4
.text:0041B6C4                 push    ebp
.text:0041B6C5                 mov     ebp, esp
.text:0041B6C7                 sub     esp, 4
                               //把首地址放入 ebp+this(此this非彼this,不要弄混淆)
.text:0041B6CA                 mov     [ebp+this], ecx

                               //return old + old2;
.text:0041B6CD                 mov     eax, [ebp+this]
.text:0041B6D0                 mov     edx, [eax]
.text:0041B6D2                 mov     eax, [ebp+this]
.text:0041B6D5                 mov     eax, [eax+4]
.text:0041B6D8                 add     eax, edx

.text:0041B6DA                 leave
.text:0041B6DB                 retn
.text:0041B6DB __ZN1A9getNumberEv endp

以下分别为getNumber()跟seeThis()的反汇编代码,部分解释在注释里

1.这里发现神奇的事情,a跟b这两个对象,共用一段函数代码。也就是说,在实例类对象时,不会重复定义函数,函数只会存在一份。那么此时又产生一个问题,如何识别当前调用的是a的函数还是b的函数?承接上面的问题,关键在ecx寄存器。ecx即为this。函数执行的时候会以这个this当做基地址+上成员变量的偏移地址。这有点像多线程中的可重入函数。当然,具体以什么寄存器作为this的保存者,具体看编译器怎么决定,大致做法都是一样的。可能这里ecx为esi,edi都有可能。

2.这里可以看到类成员函数区别于普通函数的是,编译器隐藏了一个参数传递.为以下:

.text:0041B6B0 ; void __cdecl A::seeThis(A *const this)
.text:0041B6C4 ; int __cdecl A::getNumber(A *const this)

A*const this。这是编译器通过ecx传递过去的参数。具体怎么传递,看编译器的传递方式,这里为c的调用方式。也可以用stdcall的调用方式。 

由此,初步结论。类变得不再那么神秘,它是编译器抽象化的产物,数据排列方式大致如数组,成员函数不在类中定义(但同类的对象公用一个函数代码块,这就跟调用普通函数没有任何区别),静态成员跟全局变量相同,只不过编译器做了作用域限制。this指针即为对象的首地址。一般存入某个寄存器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值