2018年6月22日 星期五

c++ member function pointer 的實作 by cfront

Re: [問題] 關於Class指標的觀念

我在這篇回應過一些想法, 不過基本上是錯誤的。

Why am I having trouble taking the address of a C++ function?

Short answer: if you’re trying to store it into (or pass it as) a pointer-to-function, then that’s the problem — this is a corollary to the previous FAQ.
Long answer: In C++, member functions have an implicit parameter which points to the object (the this pointer inside the member function). Normal C functions can be thought of as having a different calling convention from member functions, so the types of their pointers (pointer-to-member-function vs pointer-to-function) are different and incompatible. C++ introduces a new type of pointer, called a pointer-to-member, which can be invoked only by providing an object.
NOTE: do not attempt to “cast” a pointer-to-member-function into a pointer-to-function; the result is undefined and probably disastrous. E.g., a pointer-to-member-function is not required to contain the machine address of the appropriate function. As was said in the last example, if you have a pointer to a regular C function, use either a top-level (non-member) function, or a static (class) member function.
上述藍色文字在說, 將 member function pointer 轉成一般 pointer 是不可行的, 雖然我做了以上的測試, 將 member function pointer 轉成 non-member function pointer 之後, 再去執行, 看起來沒什麼問題, 但直到我使用 cfront 查看 member function pointer 實作之後, 我才真正理解為什麼這樣看起來可以運作的程式碼, 其實是錯誤的, 請不要這麼做

h1.C
 1 #define CFRONT_CPP
 2 
 3 #ifdef CFRONT_CPP
 4 #include <stream.h>
 5 #include <stdint.h>
 6 #else
 7 #include <iostream>
 8 using namespace std;
 9 #endif
10 
11 class A
12 {
13   public:
14     virtual void foo(int a = 0)
15     {
16       printf("A %d\n", a);
17     }
18     virtual void va(int a)
19     {
20       printf("va: A %d\n", a);
21     }
22     void mf1()
23     {
24       printf("mf: mf1\n");
25     }
26 };
27 
28 class B : public A
29 {
30   public:
31     virtual void foo(int a = 1)
32     {
33       printf("B a: %d\n", a);
34     }
35 };
36 
37 int main(int argc, char *argv[])
38 {
39   A a;
40   void (A::*mf)() = &A::mf1;
41 42 uintptr_t addr = *((uintptr_t*)&mf); 43 (*(void(*)(A *))(addr) )(&a); 44 (a.*mf)(); 45 46 #if 0 47 printf("sizeof(mf): %u\n", sizeof(mf)); 48 cout << "(sizeof(mf): " << sizeof(mf) << endl; 49 #endif 50 return 0; 51 }

h1.C L40 被轉成 h1..c L818

list 1
40 void (A::*mf)() = &A::mf1;
->
016 typedef int (*__vptp)(void);
017 struct __mptr {short d; short i; __vptp f; };
811 struct __mptr __1mf ;
818 ((__1mf .d=0),((__1mf .i=-1),(__1mf .f=(((int (*)(void ))mf1__1AFv)))));

一個 member function pointer 事實上並不是指標, 而是一個結構, 參考 list 1. L017 struct __mptr, 其中有 d, i, f, 3 個欄位, 而 f 才是用來指向 member function。mf1__1AFv 就是

void A::mf1()

當使用一個 member function pointer 變數時, 其實操作的不僅僅是指標 (是 struct __mptr), 裡頭還有額外的 d, i 2 個欄位, 所以用
42   uintptr_t addr = *((uintptr_t*)&mf);
43   (*(void(*)(A *))(addr) )(&a);
將一個 member function pointer 轉成一個指向 non-member function pointer, 其實並不是指標互轉, 而是把一個 struct, 裡頭有 d, i, f, 轉成一個指標, 這樣當然是不可能會正確的。

上述的語法先把 member function pointer 轉成一個整數, 再轉成 non-member function pointer 去執行。

H1.C L44 (a.*mf)(); 被轉成以下 2 行, 類似上方程式碼的轉型動作。

821 __1addr = ((*(((uintptr_t *)(& __1mf )))));
822 ((*(((void (*)(struct A *))__1addr ))))( & __1a ) ;__1mf )))));

這僅僅是 cfront 的實作方式, 不同編譯器可能會有不同的實作方式。

h1..c
001 #line 1 "h1.C"
002 
003 /* <<AT&T C++ Language System <3.0.3> 05/05/94>> */
004 char __cfront_version_303_xxxxxxxx;
005 /* < h1.C > */

016 typedef int (*__vptp)(void);
017 struct __mptr {short d; short i; __vptp f; };

805 #line 37 "h1.C"
806 int main (int __1argc , char **__1argv ){ _main(); 
807 #line 38 "h1.C"
808 { 
809 #line 39 "h1.C"
810 struct A __1a ;
811 struct __mptr __1mf ;
812 
813 #line 42 "h1.C"
814 uintptr_t __1addr ;
815 
816 #line 39 "h1.C"
817 ( ((& __1a )-> __vptr__1A = (struct __mptr *) __ptbl_vec__h1_C_[0]), (& __1a )) ;
818 ( (__1mf .d= 0 ), ( (__1mf .i= -1), (__1mf .f= (((int (*)(void ))mf1__1AFv )))) ) ;
819 
820 #line 42 "h1.C"
821 __1addr = ((*(((uintptr_t *)(& __1mf )))));
822 ((*(((void (*)(struct A *))__1addr ))))( & __1a ) ;
823 (__1mf .i< 0 )?((*(((void (*)(struct A *__0this ))__1mf .f))))( ((struct A *)((((char *)(& __1a )))+ __1mf .d))) :((*(((void (*)(struct A *__0this ))((& __1a )-> __vptr__1A [__1mf .i]).f))))(
824 #line 44 "h1.C"
825 ((struct A *)((((char *)(& __1a )))+ ((& __1a )-> __vptr__1A [__1mf .i]).d))) ;
826 
827 #line 50 "h1.C"
828 return 0 ;
829 }
830 } 
947 #line 51 "h1.C"
948 
949 /* the end */

h1.C L47 印出這個 member function pointer 大小時, 其實是印出 struct __mptr {short d; short i; __vptp f; }; 的大小, 在我的平台上, 是 16 byte。

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。