2012-06-11 55 views
5

Tôi cần chuyển đổi một số con trỏ hàm thành viên sang số void* con trỏ (vì tôi cần đẩy chúng vào ngăn Lua, nhưng vấn đề không liên quan đến Lua).Gọi con trỏ hàm thành viên C++: con trỏ này bị lỗi

Tôi thực hiện việc này bằng cách sử dụng union. Nhưng khi tôi chuyển đổi các con trỏ hàm thành viên thành một số void* và ngược lại và sau đó cố gắng gọi con trỏ với một thể hiện của lớp, con trỏ this bị hỏng. Kỳ lạ thay, vấn đề này không xảy ra, nếu tôi chuyển đổi con trỏ void* trở lại thành con trỏ hàm kiểu C với một con trỏ đến lớp đó là tham số đầu tiên của nó.

Dưới đây là đoạn mã mà chứng tỏ vấn đề:

#include <iostream> 
using namespace std; 

class test 
{ 
    int a; 

    public: 
     void tellSomething() 
     { 
      cout << "this: " << this << endl; 
      cout << "referencing member variable..." << endl; 
      cout << a << endl; 
     } 
}; 

int main() 
{ 
    union 
    { 
     void *ptr; 
     void (test::*func)(); 
    } conv1, conv2; 

    union 
    { 
     void *ptr; 
     void (*func) (test*); 
    } conv3; 

    test &t = *new test(); 

    cout << "created instance: " << (void*) &t << endl; 

    // assign the member function pointer to the first union 
    conv1.func = &test::tellSomething; 

    // copy the void* pointers 
    conv2.ptr = conv3.ptr = conv1.ptr; 

    // call without conversion 
    void (test::*func1)() = conv1.func; 
    (t.*func1)(); // --> works 

    // call with C style function pointer invocation 
    void (*func3) (test*) = conv3.func; 
    (*func3) (&t); // --> works (although obviously the wrong type of pointer) 

    // call with C++ style member function pointer invocation 
    void (test::*func2)() = conv2.func; 
    (t.*func2)(); // `this' is the wrong pointer; program will crash in the member function 

    return 0; 
} 

Đó là kết quả:

created instance: 0x1ff6010 
this: 0x1ff6010 
referencing member variable... 
0 
this: 0x1ff6010 
referencing member variable... 
0 
this: 0x10200600f 
referencing member variable... 
zsh: segmentation fault (core dumped) ./a.out 

Đây có phải là một lỗi trong trình biên dịch (GCC)? Tôi biết rằng sự chuyển đổi này giữa các con trỏ hàm void* và (thành viên) không tuân thủ tiêu chuẩn, nhưng điều kỳ lạ là nó hoạt động khi chuyển đổi void* thành con trỏ hàm kiểu C.

+3

Bạn đã kiểm tra sizeof() của memfunptr chưa? Trong thực hiện của tôi nó lớn hơn sizeof (void *) – PlasmaHH

Trả lời

5

Thêm hai dòng này để mã của bạn và câu trả lời sẽ được rõ ràng:

cout << "sizeof(void*)=" << sizeof(conv1.ptr) << endl; 
cout << "sizeof(test::*)=" << sizeof(conv1.func) << endl; 

Lý do rất đơn giản. Xem xét:

class Base1 
{ 
public: 
int x; 
void Foo(); 
Base1(); 
}; 

class Base2 
{ 
public: 
float j; 
void Bar(); 
Base2(); 
}; 

class Derived : public Base1, public Base2 
{ 
Derived(); 
}; 

Khi bạn gọi Foo trên Derived, con trỏ this phải trỏ đến Base1::x. Nhưng khi bạn gọi Bar trên số Derived, con trỏ this phải trỏ đến Base2::j! Vì vậy, một con trỏ đến một hàm thành viên phải bao gồm cả địa chỉ của hàm và một "điều chỉnh" để sửa con trỏ this để trỏ đến một thể hiện của kiểu đúng của lớp mà hàm sẽ là con trỏ this.

Bạn đang mất bộ điều chỉnh, khiến con trỏ this được điều chỉnh ngẫu nhiên.

+0

Ok, cảm ơn bạn rất nhiều, điều đó đã làm cho nó rõ ràng. Bây giờ tôi chuyển đổi các con trỏ bằng cách sử dụng một 'union {struct {void * p1, * p2 khác; } ptr; void (test :: * func)(); } '. Điều này sẽ làm việc trong mọi trường hợp? Hay tốt hơn là tìm kiếm con trỏ hàm thành viên 'sizeof' trước và sau đó quyết định có bao nhiêu' void * 'để sử dụng? –

+0

Tôi thích cách tiếp cận đầu tiên của bạn tốt hơn. –

+1

@AlfredKrohmer: có lẽ bạn chỉ cần phân bổ hàm con trỏ tới thành viên, chuyển xung quanh một con trỏ tới nó, và tìm ra cách tốt nhất để quản lý tài nguyên. Chắc chắn nếu bạn đang truyền một số 'struct' qua Lua, chứa một vài số nguyên và một con trỏ, bạn sẽ không cố gắng tìm ra bao nhiêu' void * 'được yêu cầu để thêm vào bộ nhớ của nó, và giải thích nó như một vài' void * '? Vâng, một hàm con trỏ tới thành viên là một vài số nguyên và một con trỏ. Hãy coi chừng cũng rằng trong một số triển khai, các con trỏ đến các loại hàm thành viên khác nhau có kích thước khác nhau. –

1

Lạ lùng, ở đây (dưới VS2005), cuộc gọi đầu tiên và thứ 3 hoạt động tốt nhưng lần thứ 2 (với conv3) không thành công với lỗi này bị hỏng.

+1

Là một giả đoán bay, VS2005 sử dụng quy ước gọi cdecl cho con trỏ tới hàm (vì vậy tham số được truyền trên stack), nhưng quy ước gọi thiscall với các hàm thành viên (vì vậy callee hy vọng 'this' được truyền vào ECX). Vì vậy, ngăn chặn 'conv3' làm việc ngay cả khi nó đã đưa ra địa chỉ chính xác của mã. –

+0

Đây là hành vi chính xác mà tôi mong đợi sẽ xảy ra. –

1

Có vẻ như tôi đang triển khai thực hiện, size(void*) byte đầu tiên của một cá thể của loại hàm thành viên void (test::*)() chỉ xảy ra trong trường hợp này là địa chỉ của hàm trong bộ nhớ. Như một chi tiết thực hiện, hàm đó có thể gọi được như thể nó là một hàm miễn phí với tham số đầu tiên là this. Đó là lý do tại sao conv3 dường như hoạt động.

Tuy nhiên, vận may của bạn đã hết khi cố gắng sao chép những mẫu sizeof(void*) đầu tiên này vào một phiên bản khác của loại hàm con trỏ thành thành viên. Rác chưa được khởi tạo trong phần còn lại của conv2, một khi được hiểu là phần còn lại của hàm con trỏ thành thành viên sau địa chỉ mã ban đầu, đã làm một điều gì đó sai. Tôi nghi ngờ có một số cờ và offsets trong đó, để ghi lại thông tin về các chức năng ảo và đa thừa kế và ảo. Khi thông tin đó không chính xác, mọi thứ xảy ra sai.

Các vấn đề liên quan