在介绍我的思路前, 让我们准备下预备知识

  C++是怎么实现类函数的绑定的. 我们知道类的非静态成员函数是存储在全局区, 并在内存中只保存一份副本. 我们调用非静态成员函数是通过类对象进行调用. 那么如果有两个不同的类类型有同样的成员函数, 那么编译器是怎么区别的呢? 其实编译器作了某些工作, 将类似void A::test(int) 的成员函数改成 void test(const A*, int)这样的函数

  1. class A {
  2. private:
  3. int a_;
  4.  
  5. public:
  6. A(int a):a_(a){}
  7. void run(int data);
  8.  
  9. };
  10.  
  11. void A::run(int data) {
  12.  
  13. cout<<data<<endl;
  14. cout<<a_<<endl;
  15. }
  16.  
  17. void test() {}
  18. typedef void(A::*func)(int data);
  19. typedef void(*funcc)(const A*, int data);
  20.  
  21. int main(int argc, char** argv) {
  22.  
  23. func p = &A::run;
  24. int* dp = (int*)(reinterpret_cast<void*>(p));
  25. cout<<dp<<endl;
  26.  
  27. A a();
  28. //A* ptr = NULL;
  29. funcc f = (funcc)(dp);
  30. f(&a, );
  31. //f(ptr, 8888); // 将输出8888, 但输出a_时肯定出错
  32.  
  33. return ;
  34. }

运行结果:

  在linux 下终端开启一个程序, 都是shell进程开启一个子进程, 而我们知道每个进程都拥有独立的地址空间. 也就是说, C++程序静态区存放的数据地址位置都是固定的, 或者说相对固定, 相对自己的地址空间是固定的. 所以只要我们能获取函数的入口地址, 并将地址转化成相应的函数类型指针, 传入正确的参数, 就能够实现函数反射调用了.

  先申明一下, 我所写的东西并非是工业级的应用, 纯粹是自己的技术爱好, 技术也比较稚嫩, 所以缺点肯定是有的, 效率低, 局限性大, 有效性低, 实际应用没有. 我只是很享受在深入探究的过程中, 尝试去解决问题并开阔自己视野, 丰富自己经历的过程, 虽然不一定每次都能完美地解决问题, 甚至我悲观地认为并不存在完美的解决方案. 有不足或错误的地方大家都可以交流提出指正. 言归正传.

  如何获取这些信息, 或者说元信息. 我是在看调试器原理时发现的, 在使用GDB, 或者vs调试时, 你都能看到变量的地址, 函数的地址, 而且每次都不会变化, 调试器是如何知道的呢?其实这些都是调试信息, 是编译器在编译时加入到程序中的. (这里引出第一个限制条件, 调试版本,  发行版本并不包含调试信息, 这个功能就不能实现了) , window下的pdb文件, linux下的elf文件包含这些调试信息.

  我是在linux 下尝试的, 环境是ubuntu 14 和 gcc 4.8.4. 调试信息是以dwarf格式存放在elf文件中, 可以使用objdump工具查看, 编译时加入 -g 选项表示是调试版本. 下面是一个例子

  1. class Data1 {
  2.  
  3. public:
  4.  
  5. void hello(){}
  6. void test();
  7. };
  8.  
  9. void Data1::test() {
  10.  
  11. }
  12.  
  13. int main() {
  14. }
  1. ./data1: file format elf64-x86-
  2.  
  3. Contents of the .debug_info section:
  4.  
  5. Compilation Unit @ offset 0x0:
  6. Length: 0xc3 (-bit)
  7. Version:
  8. Abbrev Offset: 0x0
  9. Pointer Size:
  10. <><b>: Abbrev Number: (DW_TAG_compile_unit)
  11. <c> DW_AT_producer : (indirect string, offset: 0x0): GNU C++ 4.8. -mtune=generic -march=x86- -g -fstack-protector
  12. <> DW_AT_language : (C++)
  13. <> DW_AT_name : (indirect string, offset: 0x46): ./data1.cpp
  14. <> DW_AT_comp_dir : (indirect string, offset: 0x52): /home/lz/Workplace/debug/reflection/demo
  15. <> DW_AT_low_pc : 0x4004ee
  16. <> DW_AT_high_pc : 0x15
  17. <> DW_AT_stmt_list : 0x0
  18. <><2d>: Abbrev Number: (DW_TAG_class_type) // 表示是一个类类型
  19. <2e> DW_AT_name : (indirect string, offset: 0x40): Data1
  20. <> DW_AT_byte_size :
  21. <> DW_AT_decl_file :
  22. <> DW_AT_decl_line :
  23. <> DW_AT_sibling : <0x6a>
  24. <><>: Abbrev Number: (DW_TAG_subprogram) // 表示是一个函数类型
  25. <3a> DW_AT_external :
  26. <3a> DW_AT_name : (indirect string, offset: 0x7b): hello
  27. <3e> DW_AT_decl_file :
  28. <3f> DW_AT_decl_line :
  29. <> DW_AT_linkage_name: (indirect string, offset: 0x97): _ZN5Data15helloEv
  30. <> DW_AT_accessibility: (public)
  31. <> DW_AT_declaration :
  32. <> DW_AT_object_pointer: <0x4d>
  33. <> DW_AT_sibling : <0x53>
  34. <><4d>: Abbrev Number: (DW_TAG_formal_parameter)
  35. <4e> DW_AT_type : <0x6a>
  36. <> DW_AT_artificial :
  37. <><>: Abbrev Number:
  38. <><>: Abbrev Number: (DW_TAG_subprogram)
  39. <> DW_AT_external :
  40. <> DW_AT_name : (indirect string, offset: 0xae): test
  41. <> DW_AT_decl_file :
  42. <> DW_AT_decl_line :
  43. <5a> DW_AT_linkage_name: (indirect string, offset: 0x86): _ZN5Data14testEv
  44. <5e> DW_AT_accessibility: (public)
  45. <5f> DW_AT_declaration :
  46. <5f> DW_AT_object_pointer: <0x63>
  47. <><>: Abbrev Number: (DW_TAG_formal_parameter)
  48. <> DW_AT_type : <0x6a>
  49. <> DW_AT_artificial :
  50. <><>: Abbrev Number:
  51. <><>: Abbrev Number:
  52. <><6a>: Abbrev Number: (DW_TAG_pointer_type)
  53. <6b> DW_AT_byte_size :
  54. <6c> DW_AT_type : <0x2d>
  55. <><>: Abbrev Number: (DW_TAG_subprogram)
  56. <> DW_AT_specification: <0x53> // 指向声明的位置, 可以得到函数名, test
  57. <> DW_AT_decl_line :
  58. <> DW_AT_low_pc : 0x4004ee // 我们的目标所在, 函数地址
  59. <7e> DW_AT_high_pc : 0xa
  60. <> DW_AT_frame_base : byte block: 9c (DW_OP_call_frame_cfa)
  61. <> DW_AT_object_pointer: <0x90>
  62. <8c> DW_AT_GNU_all_call_sites:
  63. <8c> DW_AT_sibling : <0x9d>
  64. <><>: Abbrev Number: (DW_TAG_formal_parameter) // 紧跟着函数参数类型, 第一个参数为函数所属类型的指针
  65. <> DW_AT_name : (indirect string, offset: 0x81): this
  66. <> DW_AT_type : <0x9d>
  67. <> DW_AT_artificial :
  68. <> DW_AT_location : byte block: (DW_OP_fbreg: -)
  69. <><9c>: Abbrev Number:
  70. <><9d>: Abbrev Number: (DW_TAG_const_type)
  71. <9e> DW_AT_type : <0x6a>
  72. <><a2>: Abbrev Number: (DW_TAG_subprogram)
  73. <a3> DW_AT_external :
  74. <a3> DW_AT_name : (indirect string, offset: 0xa9): main
  75. <a7> DW_AT_decl_file :
  76. <a8> DW_AT_decl_line :
  77. <a9> DW_AT_type : <0xbf>
  78. <ad> DW_AT_low_pc : 0x4004f8
  79. <b5> DW_AT_high_pc : 0xb
  80. <bd> DW_AT_frame_base : byte block: 9c (DW_OP_call_frame_cfa)
  81. <bf> DW_AT_GNU_all_call_sites:
  82. <><bf>: Abbrev Number: (DW_TAG_base_type)
  83. <c0> DW_AT_byte_size :
  84. <c1> DW_AT_encoding : (signed)
  85. <c2> DW_AT_name : int
  86. <><c6>: Abbrev Number:

  可以看出里面包含了丰富的信息, 但也看出了局限所在, 函数必须在类外定义, 才能看到函数地址所在, 这是为何我也不知道. 还有函数类型有很多种, 全局函数, 静态函数, 模板函数, 继承函数, 虚函数, 各种函数的规则可能都不一样, 我也没能完全地从那些调试信息中分辨他们的区别, 看那些信息很痛苦, 所以我目前只实现了最简单的非静态成员函数.

  通过命令行工具来访问DWARF信息这虽然有用但还不能完全令我们满意。作为程序员,我们希望知道应该如何写出实际的代码来解析DWARF格式并从中读取我们需要的信息。自然地, 你可以开始专研dwarf的规范格式, 尝试自己写个程序解析. 我会提示您这个比解析html还要复杂. 我们还是利用现成的开源库来解析吧, 况且解析这一块并不是我们的重点所在. 现成的开源库有libdwarf, 现在可以把你精力专注于libdwarf的使用文档上了, 安装libdwarf 还要依赖libelf库. 这里就不介绍了.

  好了, 现在我们获取到了函数地址, 而且是字符串形式表示的地址. 我们可以在程序编译完成后解析一次, 将信息保存到一个文件中. 有了这些信息接下来该做什么, 如何将地址转换成相应的函数类型指针, 如何实例化一个类型指针并传入该函数, 前面提到的map, 第一个参数是类型名, 第二个是相应的类型产生器, 或者容器, 这些又该怎么实现. 在下一篇中我将继续介绍讨论.

  

一种实现C++反射功能的想法(二)的更多相关文章

  1. 一种实现C++反射功能的想法(一)

    Java的反射机制很酷, 只需知道类的名字就能够加载调用. 这个功能很实用, 想象一下, 用户只需指定类的名称, 就可以动态绑定类型, 而且只需通过字符串指定, 字符串的使用可以使得用户的修改只需修改 ...

  2. 一种实现C++反射功能的想法(三)

    如何实现类型名跟类型的对应, 我们很容易想到map, 没错, 就是使用map实现的. std::map<std::string, .....>, 等下, 第二部分该填什么类型, 一个函数指 ...

  3. 单例模式的几种实现And反射对其的破坏

    一 单例模式概述 (一) 什么是单例模式 单例模式属于创建型模式之一,它提供了一种创建对象的最佳方式 在软件工程中,创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象.基本的对 ...

  4. 使用反射功能在Unity运行状态通过Inspector面板修改字段和调用方法

    使用反射功能在Unity运行状态通过Inspector面板修改字段和调用方法 效果展示 一个很简单的组件脚本 运行状态在Inspector面板可以随便修改字段和调用方法 方法调用日志 设计由来 最近在 ...

  5. asp.net导出excel-一行代码实现excel、xml、pdf、word、html、csv等7种格式文件导出功能而且美观-SNF快速开发平台

    分享: 腾讯微博  新浪微博   搜狐微博   网易微博  腾讯朋友  百度贴吧  豆瓣   QQ好友  人人网 作者:王春天  原文地址:http://www.cnblogs.com/spring_ ...

  6. Java反射获取class对象的三种方式,反射创建对象的两种方式

    Java反射获取class对象的三种方式,反射创建对象的两种方式 1.获取Class对象 在 Java API 中,提供了获取 Class 类对象的三种方法: 第一种,使用 Class.forName ...

  7. Oracle EBS WMS功能介绍(二)

    Oracle EBS WMS功能介绍(二) (版权声明,本人原创或者翻译的文章如需转载,如转载用于个人学习,请注明出处.否则请与本人联系,违者必究) 出货物流逻辑主要包括 1.      打包.能够进 ...

  8. Java反射机制demo(二)—通过Class实例化任意类的对象

    Java反射机制demo(二)—通过Class实例化任意类的对象 上一章节中,实例化了Class类对象的实例,这个部分的demo展示了如何使用Class对象的实例去获得其他类的对象的实例. 任意一个类 ...

  9. 两种开源聊天机器人的性能测试(二)——基于tensorflow的chatbot

    http://blog.csdn.net/hfutdog/article/details/78155676 开源项目链接:https://github.com/dennybritz/chatbot-r ...

随机推荐

  1. Activity声明周期容易出现的问题

    了解activity的生命周期,不仅仅是回答面试官的几个小问题:下面这篇文章不错,截取个人认为优秀的部分分享给大家,欢迎交流.感谢原作者 /** * 示例向我们展示了在 Activity 的配置改变时 ...

  2. Linux学习笔记14——使用fcntl实现文件锁定

    期末考试快要来了,Linux学习进度一下拉下来许多.今天学习的是文件锁定,在Linux中,实现文件锁定的方法很多,例如fcntl和lockf.下面主要是fcntl的调用. fcntl函数的原型是:in ...

  3. del重复数

    楼主 发表于: 2010-06-21 11:46:31 本帖最后由 luckycynthia 于 2010-06-21 11:47:46 编辑 在抓取数据后对数据进行操作的途中,有时候会碰到重复数据, ...

  4. 关于fixed-point

    今天又出现了shader的问题,编译到真机效果就没了,后来仔细还是因为浮点数精度的问题,后来仔细查找了些资料,才发现自己太粗心,没有看清楚 fixed-point 数据类型就乱用,这是个范围在 [-1 ...

  5. mysql使用mysqldump导出数据出错

    mysqldump -hlocalhost -uroot -p123456 student_info jssypk  > c:/databackup.sql 导出表结构 mysqldump  - ...

  6. Domino - SGU 101 (欧拉路径)

    题目大意:这是一个多米诺骨游戏,这个游戏的规则就是一个连着一个,现在给出 N 个多米诺,每个多米诺两边都有一个编号,相邻的多米诺的编号要一致,当然多米诺是可以翻转的(翻转就加‘-’,不翻转是‘+’), ...

  7. C#中的线程(中)-线程同步

    1.同步要领 下面的表格列展了.NET对协调或同步线程动作的可用的工具:                       简易阻止方法 构成 目的 Sleep 阻止给定的时间周期 Join 等待另一个线程 ...

  8. thymeleaf 和其它标签组合 获取数据

    thymeleaf 有很多的内置标签, 但是我们在开发中会引入其它很多标签, 这个时候, 后台数据过来了,前端 页面要怎么显示呢? 网上资料真的很少. 不过还是找到了答案:  th:attr 这个标签 ...

  9. python 3Des 加密

    import hashlib; from Crypto.Cipher import DES3 import base64 def create_key(sk): r=hashlib.md5(sk).d ...

  10. spin_lock &amp; mutex_lock的差别?

    本文由该问题引入到内核锁的讨论,归纳例如以下 为什么须要内核锁? 多核处理器下,会存在多个进程处于内核态的情况,而在内核态下,进程是能够訪问全部内核数据的,因此要对共享数据进行保护,即相互排斥处理 有 ...