先给出一段代码实现

  1. #include <iostream>
  2. using namespace std;
  3. class animal
  4. {
  5. protected:
  6. int age;
  7. public:
  8. virtual void print_age(void) = 0;
  9. };
  10. class dog : public animal
  11. {
  12. public:
  13. dog() {this -> age = 2;}
  14. ~dog() { }
  15. virtual void print_age(void) {cout<<"Wang, my age = "<<this -> age<<endl;}
  16. };
  17. class cat: public animal
  18. {
  19. public:
  20. cat() {this -> age = 1;}
  21. ~cat() { }
  22. virtual void print_age(void) {cout<<"Miao, my age = "<<this -> age<<endl;}
  23. };
  24. int main(void)
  25. {
  26. cat kitty;
  27. dog jd;
  28. animal * pa;
  29. int * p = (int *)(&kitty);
  30. int * q = (int *)(&jd);
  31. p[0] = q[0];
  32. pa = &kitty;
  33. pa -> print_age();
  34. return 0;
  35. }

【源代码输出】

代码输出是 Wang, my age = 1;

假设将 p[0] = q[0]; 换为 p[1] = q[1]; 则输出为 Miao, my age = 2。

【源代码分析】

首先,这是一个取巧的改变虚表指针的办法,它利用了C++的对象模型的特点。我们知道,一个类有了虚函数后。它会有一个虚表来维护虚函数和一个虚表指针__vptr来指向它。而这个程序利用的即是改变虚指针的指向。它首先&kitty,而且转换为int*,获得cat类的虚表首地址,相同&jd获得dog类的虚表地址,而p[0] = q[0]令指向cat的虚表首地址。一下就变成了指向dog类的虚表首地址,然后基类获取到了这个指向dog类的kitty,调用虚方法则自然调用到了dog的print_age,然后这里的age则依旧保留的是cat的。由于你仅仅是改变了虚指针指向的虚表地址,不影响member
data。

重中之重,记住一个点:类对象的首地址是虚函数指针地址,其次是变量地址。改变对象指针类型,将改变实函数,改变对象指针变量,将改变虚函数与成员变量。



其次,这代码不但依赖某些C++编译器的行为,还依赖平台的指针宽度是32位。

int * p = (int *)(&kitty);

int * q = (int *)(&jd);

p[0] = q[0];

这几句不应该用int*,而应该用intptr_t*才对。这样才干保证拷贝的是一个指针宽度的数据。而不是一个int宽度的数据。

在32位平台上。int一般是32位,而指针是32位,所以正好匹配了,程序能正常执行;

在64位平台上,假设是流行的LP64模型。int是32位而指针是64位,这里实际上仅仅拷贝了指针的一半。程序是否能正常执行就看运气了。

假设是在一个64位且小端(little endian)的平台上,那这代码拷贝的是指针的低32位。非常可能会运气好能正常执行,由于dog类与cat类的vtable可能正好在内存里处于非常近的位置,它们的地址的高32位可能正好同样,地址不同的地方都在低32位。这样这个程序就运气好能正常执行。

假设是在一个64位且大端(big endian)的平台上。那这段代码拷贝的是指针的高32位,那就全然达不到效果了。

最后。这样的题还有非常多玩法。比如说一种简单的玩法是像这样:

  1. #include <iostream>
  2. using namespace std;
  3. class animal
  4. {
  5. protected:
  6. int age;
  7. public:
  8. virtual void print_age(void) = 0;
  9. };
  10. class dog : public animal
  11. {
  12. public:
  13. dog() {this -> age = 2;}
  14. ~dog() { }
  15. virtual void print_age(void) {cout<<"Wang, my age = "<<this -> age<<endl;}
  16. };
  17. class cat: public animal
  18. {
  19. public:
  20. cat() {this -> age = 1;}
  21. ~cat() { }
  22. virtual void print_age(void) {cout<<"Miao, my age = "<<this -> age<<endl;}
  23. };
  24. int main(void)
  25. {
  26. cat kitty;
  27. dog jd;
  28. animal * pa;
  29. int * p = (int *)(&kitty);
  30. int * q = (int *)(&jd);
  31. p[0] = q[0];
  32. pa = &kitty;
  33. pa -> print_age();
  34. return 0;
  35. }

直接整个vtable伪造出来然后想往里面填啥就填啥。

  1.  

C++ 虚指针、成员变量与类对象的偏移地址的更多相关文章

  1. C++空类以及没有成员变量的类的大小

    关于C++中空类的大小为1,我们大家都有所了解,但是除了空类之外的其他一些没有成员变量的类的大小,还是有很多不明之处的. 我们来看如下一个例子: #include<iostream> us ...

  2. static 成员变量、static 成员函数、类/对象的大小

    一.static 成员变量 对于特定类型的全体对象而言,有时候可能需要访问一个全局的变量.比如说统计某种类型对象已创建的数量. 如果我们用全局变量会破坏数据的封装,一般的用户代码都可以修改这个全局变量 ...

  3. static成员变量与返回对象的引用

    (1)用static修饰类成员变量(属性),表明该变量是静态的,无论创建多少对象,都只创建一个一个静态属性副本,也就是对象们共享同一个静态属性,这个方法常用的一个用途就是用来计算程序调用了多少次这个类 ...

  4. Runtime之成员变量&属性&关联对象

    上篇介绍了Runtime类和对象的相关知识点,在4.5和4.6小节,也介绍了成员变量和属性的一些方法应用.本篇将讨论实现细节的相关内容. 在讨论之前,我们先来介绍一个很冷僻但又很有用的一个关键字:@e ...

  5. 『无为则无心』Python面向对象 — 51、私有成员变量(类中数据的封装)

    目录 1.私有成员变量介绍 (1)私有成员变量概念 (2)私有成员变量特点 (3)私有成员变量体验 2.属性私有化工作原理 3.定义成员变量的标识符规范 4.私有成员变量的获取和设置方式 1.私有成员 ...

  6. Python的类变量和成员变量、类静态方法和类成员方法

    先说明几个相关的术语:attribute.function.method. attribute:类对象的数据成员.我们经常会在Python代码出错时遇到:“AttributeError: 'My_Cl ...

  7. java对象的内存布局(二):利用sun.misc.Unsafe获取类字段的偏移地址和读取字段的值

    在上一篇文章中.我们列出了计算java对象大小的几个结论以及jol工具的使用,jol工具的源代码有兴趣的能够去看下.如今我们利用JDK中的sun.misc.Unsafe来计算下字段的偏移地址,一则验证 ...

  8. Pyton:类变量,实例变量,类对象,实例对象

    https://www.cnblogs.com/crazyrunning/p/6945183.html

  9. php 对象赋值后改变成员变量影响赋值对象

    话不多说看代码 打印结果 对obj1的操作 直接影响了obj2 , 对obj2的操作 直接影响了obj1

随机推荐

  1. 详解tomcat的连接数与线程池

    前言 在使用tomcat时,经常会遇到连接数.线程数之类的配置问题,要真正理解这些概念,必须先了解Tomcat的连接器(Connector). 在前面的文章 详解Tomcat配置文件server.xm ...

  2. ios video标签部分mp4文件无法播放的问题

    问题描述: 部分MP4文件在ios的微信浏览器中无法播放,点击播放后缓冲一下之后显示叉,而另外一些mp4文件正常,同时在安卓全部下正常. 分析: h264编码的压缩级别问题导致. 苹果官方文档中对 i ...

  3. 初生牛犊不怕虎 golang入坑系列

    读前必读,下面所有内容都是来自这里. 放到这里的目的,就是为了比对一下,哪里的读者多.平心而论,同样的Markdown,博客园排版真心X看,怎么瞅怎么X看.(X := '难' || X :='耐' | ...

  4. csv格式的数据存储到mysql

    python写的,有点冗余,先码出来~~~~ 这是data_stored.py的代码 # -*- coding:utf-8 -*- # 存数据到mysql (只存了时间数字) import pymys ...

  5. How To Use Linux epoll with Python

    http://scotdoyle.com/python-epoll-howto.html Line 1: The select module contains the epoll functional ...

  6. JMS学习之路(一):整合activeMQ到SpringMVC 转载:http://www.cnblogs.com/xiaochangwei/p/5426639.html

    JMS的全称是Java Message Service,即Java消息服务.它主要用于在生产者和消费者之间进行消息传递,生产者负责产生消息,而消费者负责接收消息.把它应用到实际的业务需求中的话我们可以 ...

  7. [最短路][部分转]P1027 Car的旅行路线

    题目描述 又到暑假了,住在城市A的Car想和朋友一起去城市B旅游.她知道每个城市都有四个飞机场,分别位于一个矩形的四个顶点上,同一个城市中两个机场之间有一条笔直的高速铁路,第I个城市中高速铁路了的单位 ...

  8. UITextField的使用小技巧

    [tf setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];//修改placeHolder ...

  9. Elasticsearch中Head插件的使用

    在学习Elasticsearch的过程中,必不可少需要通过一些工具查看es的运行状态以及数据.如果都是通过rest请求,未免太过麻烦,而且也不够人性化.此时,head可以完美的帮助你快速学习和使用El ...

  10. Redis各个数据类型的使用场景

    Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合). Redis列表命令 参考:http://www.r ...