程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 一種實現C++反射功能的想法(二),想法

一種實現C++反射功能的想法(二),想法

編輯:C++入門知識

一種實現C++反射功能的想法(二),想法


  在介紹我的思路前, 讓我們准備下預備知識

  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(6666);
28         //A* ptr = NULL;
29     funcc f = (funcc)(dp);
30     f(&a, 100);
31         //f(ptr, 8888);   // 將輸出8888, 但輸出a_時肯定出錯
32 
33         return 0;  
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 }
./data1:     file format elf64-x86-64

Contents of the .debug_info section:

  Compilation Unit @ offset 0x0:
   Length:        0xc3 (32-bit)
   Version:       4
   Abbrev Offset: 0x0
   Pointer Size:  8
 <0><b>: Abbrev Number: 1 (DW_TAG_compile_unit)
    <c>   DW_AT_producer    : (indirect string, offset: 0x0): GNU C++ 4.8.4 -mtune=generic -march=x86-64 -g -fstack-protector    
    <10>   DW_AT_language    : 4    (C++)
    <11>   DW_AT_name        : (indirect string, offset: 0x46): ./data1.cpp    
    <15>   DW_AT_comp_dir    : (indirect string, offset: 0x52): /home/lz/Workplace/debug/reflection/demo    
    <19>   DW_AT_low_pc      : 0x4004ee    
    <21>   DW_AT_high_pc     : 0x15    
    <29>   DW_AT_stmt_list   : 0x0    
 <1><2d>: Abbrev Number: 2 (DW_TAG_class_type)   // 表示是一個類類型
    <2e>   DW_AT_name        : (indirect string, offset: 0x40): Data1    
    <32>   DW_AT_byte_size   : 1    
    <33>   DW_AT_decl_file   : 1    
    <34>   DW_AT_decl_line   : 1    
    <35>   DW_AT_sibling     : <0x6a>    
 <2><39>: Abbrev Number: 3 (DW_TAG_subprogram)   // 表示是一個函數類型
    <3a>   DW_AT_external    : 1    
    <3a>   DW_AT_name        : (indirect string, offset: 0x7b): hello    
    <3e>   DW_AT_decl_file   : 1    
    <3f>   DW_AT_decl_line   : 5    
    <40>   DW_AT_linkage_name: (indirect string, offset: 0x97): _ZN5Data15helloEv    
    <44>   DW_AT_accessibility: 1    (public)
    <45>   DW_AT_declaration : 1    
    <45>   DW_AT_object_pointer: <0x4d>    
    <49>   DW_AT_sibling     : <0x53>    
 <3><4d>: Abbrev Number: 4 (DW_TAG_formal_parameter)
    <4e>   DW_AT_type        : <0x6a>    
    <52>   DW_AT_artificial  : 1    
 <3><52>: Abbrev Number: 0
 <2><53>: Abbrev Number: 5 (DW_TAG_subprogram)
    <54>   DW_AT_external    : 1    
    <54>   DW_AT_name        : (indirect string, offset: 0xae): test    
    <58>   DW_AT_decl_file   : 1    
    <59>   DW_AT_decl_line   : 6    
    <5a>   DW_AT_linkage_name: (indirect string, offset: 0x86): _ZN5Data14testEv    
    <5e>   DW_AT_accessibility: 1    (public)
    <5f>   DW_AT_declaration : 1    
    <5f>   DW_AT_object_pointer: <0x63>    
 <3><63>: Abbrev Number: 4 (DW_TAG_formal_parameter)
    <64>   DW_AT_type        : <0x6a>    
    <68>   DW_AT_artificial  : 1    
 <3><68>: Abbrev Number: 0
 <2><69>: Abbrev Number: 0
 <1><6a>: Abbrev Number: 6 (DW_TAG_pointer_type)
    <6b>   DW_AT_byte_size   : 8    
    <6c>   DW_AT_type        : <0x2d>    
 <1><70>: Abbrev Number: 7 (DW_TAG_subprogram)
    <71>   DW_AT_specification: <0x53>      // 指向聲明的位置, 可以得到函數名, test
    <75>   DW_AT_decl_line   : 9    
    <76>   DW_AT_low_pc      : 0x4004ee    // 我們的目標所在, 函數地址
    <7e>   DW_AT_high_pc     : 0xa    
    <86>   DW_AT_frame_base  : 1 byte block: 9c     (DW_OP_call_frame_cfa)
    <88>   DW_AT_object_pointer: <0x90>    
    <8c>   DW_AT_GNU_all_call_sites: 1    
    <8c>   DW_AT_sibling     : <0x9d>    
 <2><90>: Abbrev Number: 8 (DW_TAG_formal_parameter)   // 緊跟著函數參數類型, 第一個參數為函數所屬類型的指針
    <91>   DW_AT_name        : (indirect string, offset: 0x81): this    
    <95>   DW_AT_type        : <0x9d>    
    <99>   DW_AT_artificial  : 1    
    <99>   DW_AT_location    : 2 byte block: 91 68     (DW_OP_fbreg: -24)
 <2><9c>: Abbrev Number: 0
 <1><9d>: Abbrev Number: 9 (DW_TAG_const_type)
    <9e>   DW_AT_type        : <0x6a>    
 <1><a2>: Abbrev Number: 10 (DW_TAG_subprogram)
    <a3>   DW_AT_external    : 1    
    <a3>   DW_AT_name        : (indirect string, offset: 0xa9): main    
    <a7>   DW_AT_decl_file   : 1    
    <a8>   DW_AT_decl_line   : 13    
    <a9>   DW_AT_type        : <0xbf>    
    <ad>   DW_AT_low_pc      : 0x4004f8    
    <b5>   DW_AT_high_pc     : 0xb    
    <bd>   DW_AT_frame_base  : 1 byte block: 9c     (DW_OP_call_frame_cfa)
    <bf>   DW_AT_GNU_all_call_sites: 1    
 <1><bf>: Abbrev Number: 11 (DW_TAG_base_type)
    <c0>   DW_AT_byte_size   : 4    
    <c1>   DW_AT_encoding    : 5    (signed)
    <c2>   DW_AT_name        : int    
 <1><c6>: Abbrev Number: 0

 

   可以看出裡面包含了豐富的信息, 但也看出了局限所在, 函數必須在類外定義, 才能看到函數地址所在, 這是為何我也不知道. 還有函數類型有很多種, 全局函數, 靜態函數, 模板函數, 繼承函數, 虛函數, 各種函數的規則可能都不一樣, 我也沒能完全地從那些調試信息中分辨他們的區別, 看那些信息很痛苦, 所以我目前只實現了最簡單的非靜態成員函數.

  通過命令行工具來訪問DWARF信息這雖然有用但還不能完全令我們滿意。作為程序員,我們希望知道應該如何寫出實際的代碼來解析DWARF格式並從中讀取我們需要的信息。自然地, 你可以開始專研dwarf的規范格式, 嘗試自己寫個程序解析. 我會提示您這個比解析html還要復雜. 我們還是利用現成的開源庫來解析吧, 況且解析這一塊並不是我們的重點所在. 現成的開源庫有libdwarf, 現在可以把你精力專注於libdwarf的使用文檔上了, 安裝libdwarf 還要依賴libelf庫. 這裡就不介紹了.

  好了, 現在我們獲取到了函數地址, 而且是字符串形式表示的地址. 我們可以在程序編譯完成後解析一次, 將信息保存到一個文件中. 有了這些信息接下來該做什麼, 如何將地址轉換成相應的函數類型指針, 如何實例化一個類型指針並傳入該函數, 前面提到的map, 第一個參數是類型名, 第二個是相應的類型產生器, 或者容器, 這些又該怎麼實現. 在下一篇中我將繼續介紹討論.

  

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved