C++成员不通过对象调用的直接调用写法

C++成员不通过对象调用(.或->方式)的另类(C式)调用写法

#include <iostream>
using namespace std;

/*
我们知道,成员函数和普通函数最大的区别就是成员函数包含一个隐藏的参数this指针,用来表明成员函
数当前作用在那一个对象实例上。

根据调用约定(Calling Convention)的不同,成员函数实现this指针的方式也不同。

1.    如果使用__thiscall调用约定,那么this指针保存在寄存器ECX中,VC编译器缺省情况下就是这样的。
2.    如果是__stdcall或__cdecl调用约定,this指针将通过栈进行传递,且this指针是最后一个被压入栈
的参数,相当于编译器在函数的参数列表中最左边增加了一个this参数。
*/
class Base {
public:
virtual void f() { cout << "Base::f()" << endl; }
virtual void g() { cout << "Base::g()" << endl; }
virtual void h() { cout << "Base::h()" << endl; }

virtual void foo(Base *pThis) {
pThis->hello(this);
}
virtual void hello(Base *pThis) {
pThis->h();
}

//virtual void __stdcall hello(Base *pThis) {} //成员函数指定了__stdcall调用约定
};

int test()
{
typedef void(*Fun)();

Base *b = new Base;
cout << *(int*)(&b) << endl; //虚函数表的地址存放在对象最开始的位置

Fun funf = (Fun)(*(int*)*(int*)b);
Fun fung = (Fun)(*((int*)*(int*)b + 1));
Fun funh = (Fun)(*((int*)*(int*)b + 2));

/************************************************************************/
/*    调用内部无this参与的成员(包括变量和方法)的对象方法    */
/************************************************************************/

//如果下面三个方法里,没有用到与对象相关的成员可以不用为ecx赋值,否则会出错
funf();
fung();
funh();

/************************************************************************/
/* 调用内部有this参与的成员(包括变量和方法)的对象方法 */
/************************************************************************/

//少了__stdcall(注意位置),栈会不平衡了:本来c++默认是thiscall,如果不要,vs编译器会让调用者平衡栈,即多了一句 add esp, 4
typedef void(__stdcall *Fun_Base)(Base*);
Fun_Base foo = Fun_Base(*((int*)*(int*)b + 3));

//就是多增加这句,因为编译器对c++默认采用thiscall
_asm{
mov ecx, dword ptr[b]
}

foo(b);//里面用到this了,不给ecx赋值,this就不对

/************************************************************************/
/* 调用内部有this参与的成员(包括变量和方法)的对象方法,纯汇编版本    */
/************************************************************************/

//尝试调用虚函数表的第四个方法
_asm{
//同上,如果所call的方法里,没有用到与对象相关的成员可以不用为ecx赋值,否则会出错
mov ecx, dword ptr[b]

push ecx    //一个入参

//mov ecx, b或mov eax, [b],表达同一个意思,vs最后都是mov ecx, dword ptr[b];
//实质是mov eax, [EBP-04h],结果表现为ecx=b,即从栈上获取指针b的值,而不是*b(即*(int*)b == ptr_vftable)的值。
mov eax, [b]    //获取对象指针
mov eax, [eax]    //虚函数表首地址,即对象的开始处,ptr_vftable = [eax + 0] = [ecx] = [this_of_b] = [b]
call [eax + 0x0c]    //调用虚函数表的第四个,某虚函数表里第N个方法(x86),[ptr_vftable + (N - 1) * 4] = [[eax]+(N-1)*4]
}

/************************************************************************/
/* 正常的调用 */
/************************************************************************/

/*
00EF9886 8B 45 F4 mov eax, dword ptr[b]
00EF9889 50 push eax
00EF988A 8B 4D F4 mov ecx, dword ptr[b]
00EF988D 8B 11 mov edx, dword ptr[ecx]
00EF988F 8B 4D F4 mov ecx, dword ptr[b]
00EF9892 8B 42 0C mov eax, dword ptr[edx + 0Ch]
00EF9895 FF D0 call eax
*/
b->foo(b);

//http://blog.csdn.net/haoel/article/details/1948051/
/*
虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。
这个结束标志的值在不同的编译器下是不同的。

1. 在WinXP+VS2003下,这个值是NULL。
2. 而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,
2.1 这个值是如果1,表示还有下一个虚函数表,
2.2 如果值是0,表示是最后一个虚函数表。
*/
cout << (Fun)(*((int*)*(int*)b + 5)); // 最后一个位置为0,表明虚函数表的结束

return 0;
}

C++虚函数介绍

1. 先从C++看看下虚函数表和多态表现 http://blog.csdn.net/haoel/article/details/1948051/

2. 有虚函数的类的大小 http://blog.csdn.net/hackbuteer1/article/details/7883531

3. 再从汇编看,类的继承,虚函数表的形成,表放在哪个地址等 http://www.pediy.com/kssd/pediy10/60538.html

虚函数表的形成,是在类构造函数里,对实例对象首地址里存放的虚函数数组进行修改实现的。

class Child :public Base{
public:
virtual void vf1(){cout<<"I‘m in sub Class.";}
virtual void vf2(){cout<<"I‘m in sub Class.";}
//...
}
.text                     .rdata?
class Child内存           这个类的所有对象所共有的
+-------------+          +-----+-----+-----+-----+----+
| ptr_vftable | -------> | vf1 | vf2 | vf3 | ... |end?|
+-------------+          +-----+-----+-----+-----+----+
|             |
|other members|
|             |
+-------------+

逆向C++(中文版)
http://wenku.baidu.com/link?url=bjLVj2eqfe29_Edzi99MBGJeoCtVaHDXj-3r4s4lm771BAQnJ0WIUaQywPZgGq3Yz_uU9yh-B0V6q5SFMUhRo0t436BUnUdaHuhpwERvLvC

C++多态性:

GoF著作中未提到的设计模式(4):Double Dispatch
http://www.cnblogs.com/west-link/archive/2011/07/26/2116887.html
http://en.wikipedia.org/wiki/Double_dispatch

时间: 2024-09-29 18:44:39

C++成员不通过对象调用的直接调用写法的相关文章

静态方法不需要有对象,可以使用类名调用

Public static void printData(){} 表明此类方法为类方法(静态方法) 静态方法不需要有对象,可以使用类名调用. 静态方法中不允许访问类的非静态成员,包括成员的变量和方法,因为此时是通过类调用的,没有对象的概念.This.data是不可用的. 一般情况下,主方法是静态方法,所以可调用静态方法,主方法为静态方法是因为它是整个软件系统的入口,而进入入口时系统中没有任何对象,只能使用类调用. 1 package TomText; 2 //使用do-while语句,完成简单的

Java中 对象的创建于调用

Main方法是程序的主入口,想要用某个方法必须在main方法中调用 创建对象: 类名 对象名 = new 类名(); 使用对象访问类中的成员: 对象名.成员变量: 对象名.成员方法(); 成员变量的默认值 对象的使用格式举例 public class StudentTest_01 { public static void main(String[] args) { //创建对象格式,类名 对象名 = new 类名(); //无参数使用方法 Student s2 = new Student();

[转]关于Unity3D对象和脚本实例调用的顺序探究

http://blog.csdn.net/liangzg_2011/article/details/8150844 关于Unity3D对象和脚本实例调用的顺序探究 我们先来看一些有趣Unity实例顺序的小实验.有图有真相!! 注:以上打印的代码语句如下: [csharp] view plaincopy <span style="font-size:18px;">    void Start () { print("-----" + this.transf

[原创]java WEB学习笔记59:Struts2学习之路---OGNL,值栈,读取对象栈中的对象的属性,读取 Context Map 里的对象的属性,调用字段和方法,数组,list,map

本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 -----------------------------------------------------------------------------------------------------------------

JavaScript作用域、上下文环境、函数对象的定义与调用、匿名函数的定义与调用、闭包

提到闭包总给人很高深的感觉,网上的例子也数不胜数.但是我发现相当一部分并不容易理解.根据我的观察,是因为这些例子把标题中提到的概念糅杂在了一起,往往越看越糊涂.所以我希望化整为零,拆成简单例子来解释. 1.先看作用域: JavaScript作用域只有两种--全局作用域和函数内作用域,没有代码块作用域.示例: function loop(){ for(var i=0;i<5;i++){ //doSomething; } alert(i); } loop(); //执行函数结果为5. 尽管变量i已经

使用反射构造对象实例并动态调用方法

在.Net 中,程序集(Assembly)中保存了元数据(MetaData)信息,因此就可以通过分析元数据来获取程序集中的内容,比如类,方法,属性等,这大大方便了在运行时去动态创建实例. MSDN解释如下: 反射提供了封装程序集.模块和类型的对象(Type 类型).可以使用反射动态创建类型的实例,将类型绑定到现有对象,或从现有对象获取类型并调用其方法或访问其字段和属性.如果代码中使用了属性,可以利用反射对它们进行访问. 主要用途: 动态加载DLL,实现插件机制. 实例化DLL中的类型. 执行后期

Objective-C学习笔记(二十)——成员变量对对象方法与类方法的可见性分析

之前在学习C++,或者java的时候,都会遇到一个可见性的问题,这篇文章我们来讨论声明的成员变量对对象方法(减号方法)和类方法(加号方法)的可见性问题. (一)代码一:成员变量对减号方法可见,对加号方法不可见. 在People.m中声明一个成员变量:NSString *_peopleName;然后发现可以在减号方法中访问该变量,在加号方法中不可以访问该变量. -(void)ObjectShow{ NSLog(@"我是对象方法"); [email protected]"成员变量

构造函数、拷贝构造函数和析构函数的的调用时刻及调用顺序

构造函数.拷贝构造函数和析构函数的的调用时刻及调用顺序 对象是由“底层向上”开始构造的,当建立一个对象时,首先调用基类的构造函数,然后调用下一个派生类的构造函数,依次类推,直至到达派生类次数最多的派生次数最多的类的构造函数为止.因为,构造函数一开始构造时,总是要调用它的基类的构造函数,然后才开始执行其构造函数体,调用直接基类构造函数时,如果无专门说明0,就调用直接基类的默认构造函数.在对象析构时,其顺序正好相反.   下面简单介绍下这三个函数. 构造函数       1.构造函数不能有返回值  

方法的直接调用,反射调用

想调用一个方法很容易,直接代码调用就行,这人人都会.其次呢,还可以使用反射.不过通过反射调用的性能会远远低于直接调用——至少从绝对时间上来看的确是这样.虽然这是个众所周知的现象,我们还是来写个程序来验证一下.比如我们现在新建一个Console应用程序,编写一个最简单的Call方法. class Program { static void Main(string[] args) { } public void Call(object o1, object o2, object o3) { } }