免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3569 | 回复: 7
打印 上一主题 下一主题

[C++] 虚函数是怎么取得this参数的? [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-04-19 16:39 |只看该作者 |倒序浏览
20可用积分
在研究对象的内存模型,碰到这样一个问题。我通过强制类型转换的方法能够访问到对象的虚函数表指针,进而访问虚函数表。程序如下
#include<stdio.h>
struct a{
int x;
virtual void f(){printf("f(),%d\n",x);}//简单起见,只有一个虚函数
explicit a(int xx){x=xx;}
};
int main(void){
  a a1(2);
  a a2(3);
  int* pi=(int*)&a1;     //gcc编译的结果中,a1的第一个4字节是保存了虚函数表的指针
  int* pvt=(int*)pi[0];  
  typedef void(*pf)();
  pf p=(pf)pvt[0];        //虚表的指针第一项就是void f(){printf"f(),%d\n",x);}
  (*p)();                      //调用了f()

  int *p2=(int*)&a2;
  int *pv2=(int*)p2[0];
  pf px=(pf)pv2[0];
  (*px)();
  return 0;
}

程序的输出是:
f(),3
f(),3

问题:
如果我的程序去掉和a2相关的一半代码,那么程序的输出就是f(),2。我用指针强制转换的时候,调用(*p)();,编译器应该不知到this指针应该是指向a1还是a2吧,那么我的Main程序里面只有a1的时候,能输出a1.x,当有了a2的时候,就输出两遍a2.x。

什么原因呢?

最佳答案

查看完整内容

为什么大家都喜欢干这种事 …… 这是严重依赖编译器的,C++标准甚至都没要求是要用虚函数表来实现虚函数机制。gcc3.4.x 是通过给参数列表增添一个隐藏参数, 来传递this的, 代码 :/*----------------------------------------------------------------------------*/class C { int i_;public: explicit C(int i) :i_(i) {} virtual ~C() {} virtual void f() { printf("C::f(%d)\n",i_); }};#if defined(__GNUC__)# ...

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
2 [报告]
发表于 2009-04-19 16:39 |只看该作者
为什么大家都喜欢干这种事 ……  这是严重依赖编译器的,C++标准甚至都没要求是要用虚函数表来实现虚函数机制。

gcc3.4.x 是通过给参数列表增添一个隐藏参数, 来传递this的, 代码 :

/*----------------------------------------------------------------------------*/

class C {
    int i_;
public:
    explicit C(int i) :i_(i) {}
    virtual ~C() {}
    virtual void f() { printf("C::f(%d)\n",i_); }
};

#if defined(__GNUC__)

#if __GNUC__!=3
#error not test on other gcc version except gcc3.4
#endif


#include <assert.h>
#include <string.h>
#include <stdint.h>

int main()
{
    C c1(1212);
    C c2(326);

    typedef void (* virtual_function)(C*);
    // gcc 通过一个增加一个额外参数, 传递this
    // virtual_function 即是C的虚函数签名

    struct
    {
        virtual_function* vptr;
        // 虚函数表指针
        // 当然,它指向的表不全是函数, 还有RTTI信息
        // 总之, 它就是这个类的标识, 唯一的“类型域

        int i;
        // data member
    } caster;
    // 我们猜想, gcc将虚函数表指针安排在对象的最前面。

    memcpy(&caster,&c1,sizeof(caster));
    printf("c1.i_ = %d\n",caster.i); // 1212
    printf("c1.vptr_ = %p\n"
        ,reinterpret_cast<void*>(reinterpret_cast<intptr_t>(caster.vptr)) );
    virtual_function* vptr1 = caster.vptr;

    memcpy(&caster,&c2,sizeof(caster));
    printf("c2.i_ = %d\n",caster.i);
    printf("c2.vptr_ = %p\n",(void*)caster.vptr);
    virtual_function* vptr2 = caster.vptr;
   
    assert(vptr1==vptr2);
    // 显然, 它们都是C, 所以vptr指向相同的地址

    vptr1[2](&c1); // C::f(1212)
    vptr2[2](&c2); // C::f(326)
    // 我们再猜想 f在虚函数表中的第2项
}




#elif defined(_MSC_VER)


#include <assert.h>
#include <stddef.h>
#include <string.h>

int main()
{
    C c1(1212);
    C c2(326);
    typedef void (__stdcall* virtual_function)(void);
    // msvc 通过ecx传递this, 所以参数列表和虚函数相同
    // 同时, msvc生成的虚函数, 会平衡堆栈
    // 所以这里使用 __stdcall  让调用者不做堆栈的平衡工作

    struct {
        virtual_function* vptr;
        int i;
    } caster;
    // 这同样是对编译器生成代码的一种假设依赖

    memcpy(&caster,&c1,sizeof(caster));
    printf("c1.i_ = %d\n",caster.i);    // 1212
    virtual_function* vptr1 = caster.vptr;
    printf("c1.vptr_ = %p\n"
        ,reinterpret_cast<void*>(reinterpret_cast<ptrdiff_t>(vptr1)) );

    memcpy(&caster,&c2,sizeof(caster));
    printf("c2.i_ = %d\n",caster.i);    // 326
    virtual_function* vptr2 = caster.vptr;
    printf("c2.vptr_ = %p\n"
        ,reinterpret_cast<void*>(reinterpret_cast<ptrdiff_t>(vptr2)) );
    assert(vptr1==vptr2);
    // 显然 c1 c2 都是 C,它们的虚指针是相同的
   

    // 但是, 直接调用是不行的, 因为没传递this
    //vptr1[2]();

    // 这样也不行
    //_asm { lea ecx, c1 }
    // 因为下面这行代码, 修改了 ecx
    // vptr1[2]();

    // 所以要如下进行直接调用
    virtual_function f1 = vptr1[1];
    _asm {
        lea ecx,c1
        call f1
    }
    virtual_function f2 = vptr2[1];
    _asm {
        lea ecx,c2
        call f2
    }
    // 分别打印出 C::f(1212),C::f(326)
    // 同时, C::f在虚表的第1项, vs的watch窗口说的 ……
}

#else
#error unknown compiler
#endif

/*----------------------------------------------------------------------------*/


测试的编译器:

gcc (GCC) 3.4.2 (mingw-special)
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

[ 本帖最后由 OwnWaterloo 于 2009-4-19 22:05 编辑 ]

论坛徽章:
0
3 [报告]
发表于 2009-04-19 17:27 |只看该作者
这段代码会coredump,需要在
int *p2=(int*)&a2;
之前再定义一个a对象,如:
a a3(33);
(*px)();输出的将是33

论坛徽章:
9
摩羯座
日期:2013-08-15 15:18:48狮子座
日期:2013-09-12 18:07:47金牛座
日期:2013-09-16 13:23:09辰龙
日期:2013-10-09 09:03:27白羊座
日期:2013-10-17 13:32:44子鼠
日期:2014-04-23 15:09:38戌狗
日期:2014-09-17 11:37:542015年亚洲杯之韩国
日期:2015-03-26 10:16:442015亚冠之武里南联
日期:2015-08-18 14:55:52
4 [报告]
发表于 2009-04-19 17:28 |只看该作者
你这样调用,当然是不可能得到this参数了,因为调用方法错了,没挂就已经是运气了。
应该这样定义:
typedef void(a::*pf)();
这么调用
(a1.*p)();
其中pf p=(pf)pvt[0]这个可能通不过编译,不过可以pf p=(pf&)pvt[0]这样

评分

参与人数 1可用积分 +2 收起 理由
jeanlove + 2 我很赞同

查看全部评分

论坛徽章:
0
5 [报告]
发表于 2009-04-19 17:37 |只看该作者
原帖由 Aquester 于 2009-4-19 17:27 发表
这段代码会coredump,需要在
int *p2=(int*)&a2;
之前再定义一个a对象,如:
a a3(33);
(*px)();输出的将是33


hehe,可能在不同的机器上会结果不一样吧,没仔细考虑。

论坛徽章:
9
摩羯座
日期:2013-08-15 15:18:48狮子座
日期:2013-09-12 18:07:47金牛座
日期:2013-09-16 13:23:09辰龙
日期:2013-10-09 09:03:27白羊座
日期:2013-10-17 13:32:44子鼠
日期:2014-04-23 15:09:38戌狗
日期:2014-09-17 11:37:542015年亚洲杯之韩国
日期:2015-03-26 10:16:442015亚冠之武里南联
日期:2015-08-18 14:55:52
6 [报告]
发表于 2009-04-19 17:50 |只看该作者
原帖由 jeanlove 于 2009-4-19 17:37 发表


hehe,可能在不同的机器上会结果不一样吧,没仔细考虑。


this指针通过ecx传递,你原先的写法并没有将a1或者a2的地址给ecx,所以ecx是多少,完全取决于RP。

评分

参与人数 1可用积分 +2 收起 理由
jeanlove + 2 我很赞同

查看全部评分

论坛徽章:
0
7 [报告]
发表于 2009-04-19 18:14 |只看该作者
原帖由 w_anthony 于 2009-4-19 17:50 发表


this指针通过ecx传递,你原先的写法并没有将a1或者a2的地址给ecx,所以ecx是多少,完全取决于RP。


说得很对,x86上.

[ 本帖最后由 Aquester 于 2009-4-19 18:25 编辑 ]

论坛徽章:
0
8 [报告]
发表于 2009-04-20 10:36 |只看该作者
原帖由 OwnWaterloo 于 2009-4-19 21:35 发表
为什么大家都喜欢干这种事 ……  这是严重依赖编译器的,C++标准甚至都没要求是要用虚函数表来实现虚函数机制。

gcc3.4.x 是通过给参数列表增添一个隐藏参数, 来传递this的, 代码 :

/*--------------- ...

滑铁卢兄的解释,强大... ...

以前书上总是说虚函数表什么的,还认为这种实现是标准定义的,看来只是实现相关的而已,不同编译器的结果之间还不能互相通用... ...
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP