1. 程式人生 > >c++ 如何把this指標傳入成員函式 像全域性函式一樣呼叫成員函式

c++ 如何把this指標傳入成員函式 像全域性函式一樣呼叫成員函式

測試這個功能的初衷是測試boost裡面的bind

boost::bind((&A::sum), &a, _1, _2)

上面的程式碼是我boost bind及多執行緒這篇部落格裡面的一行程式碼。我就想boost是怎麼做到這樣呼叫一個類的成員函式的。其實成員函式和全域性函式無非就是差一個this指標引數。給傳進去不就也可以呼叫了。然而並沒有那麼簡單。看了boost的源碼錶示太長了。沒怎麼看懂

然後就自己寫程式碼測試了一下。還用了彙編。。

程式碼參考  http://www.cppblog.com/woaidongmao/archive/2010/03/11/109444.aspx 

上面講有兩種呼叫方法,一種是 把this指標放入ecx暫存器,一種是把this指標當作最後一個引數壓入棧

我的電腦環境是ubuntu 64位, 從生成的彙編來看,並不是壓入ecx,而是壓入rdi。 r開頭的暫存器都是64位的。然後我就想模擬成員函式的呼叫方法,手工壓入一個this指標,第一次嘗試程式碼如下。執行報segment fault。檢視彙編程式碼發現。在傳遞引數後edi被用到了。導致之前手工壓入的this被破壞了。。。。

#include <cstdio>

using namespace std;

class tt{
    public:
    /*    void foo(int x) {
            printf("arg: x=%d\n", x);
        }*/

        void foo(int x, char c = 10, char * s = "hello") {
            printf("_m_s=%d %d %c %s\n", _m_s, x, c, s);
        }
        int _m_s;
};

typedef void (tt::* FUNPTR)(int , char, char*);
typedef void (*GLOBALPTR)(int , char, char*);

template< class ToType, class FromType>
void GetMemberFuncAddr(ToType& addr, FromType from) {
    union{
        FromType _f;
        ToType _t;
    } ut; //使用union繞過c++的型別檢查
    ut._f = from;
    addr = ut._t;
}

long long This;
int main () {
    tt t;
    t._m_s =123;
    char *ptrc = "hello";
    FUNPTR ptr = &tt::foo;
    (t.*ptr)(10, 'a', ptrc);
    printf("%x\n", ptr);
    long long p;
    //p = (int)(&tt::foo); 型別不匹配不能強制型別轉換
    GetMemberFuncAddr(p, &tt::foo);
    printf("%x\n", p);

    GLOBALPTR p1 = (GLOBALPTR)p;
//    p1(10000, 'c', ptrc);
    This = (long long)&t;
    __asm__
    (
        "movq This, %rdi \n"
    );
    p1(10000, 'c', ptrc);

    return 0;
}
g++ -O1 -S test_thisPtr.cpp 生成彙編程式碼
.file	"test_thisPtr.cpp"
	.section	.rodata.str1.1,"aMS",@progbits,1
.LC0:
	.string	"_m_s=%d %d %c %s\n"
	.section	.text._ZN2tt3fooEicPc,"axG",@progbits,_ZN2tt3fooEicPc,comdat
	.align 2
	.weak	_ZN2tt3fooEicPc
	.type	_ZN2tt3fooEicPc, @function
_ZN2tt3fooEicPc:
.LFB30:
	.cfi_startproc
	subq	$8, %rsp
	.cfi_def_cfa_offset 16
	movq	%rcx, %r9
	movsbl	%dl, %r8d
	movl	%esi, %ecx
	movl	(%rdi), %edx
	movl	$.LC0, %esi
	movl	$1, %edi
	movl	$0, %eax
	call	__printf_chk
	addq	$8, %rsp
	.cfi_def_cfa_offset 8
	ret
	.cfi_endproc
.LFE30:
	.size	_ZN2tt3fooEicPc, .-_ZN2tt3fooEicPc
	.section	.rodata.str1.1
.LC1:
	.string	"hello"
.LC2:
	.string	"%x\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB32:
	.cfi_startproc
	subq	$24, %rsp
	.cfi_def_cfa_offset 32
	movl	$123, (%rsp)
	//下面5行 顯示正常點用成員函式的彙編程式碼 (t.*ptr)(10, 'a', ptrc)
	movl	$.LC1, %ecx
	movl	$97, %edx
	movl	$10, %esi
	movq	%rsp, %rdi
	call	_ZN2tt3fooEicPc
	
	movl	$_ZN2tt3fooEicPc, %edx
	movl	$0, %ecx
	movl	$.LC2, %esi
	movl	$1, %edi
	movl	$0, %eax
	call	__printf_chk
	movl	$_ZN2tt3fooEicPc, %edx
	movl	$.LC2, %esi
	movl	$1, %edi
	movl	$0, %eax
	call	__printf_chk
	movq	%rsp, This(%rip)
#APP
# 49 "test_thisPtr.cpp" 1
	movq This, %rdi

# 0 "" 2
#NO_APP
    //下面4行顯示p1(10000, 'c', ptrc); 呼叫的程式碼
    //少了 movq %rsp, %rdi命令,即壓入this指標
	movl	$.LC1, %edx
	movl	$99, %esi
	movl	$10000, %edi
	call	_ZN2tt3fooEicPc
	movl	$0, %eax
	addq	$24, %rsp
	.cfi_def_cfa_offset 8
	ret
	.cfi_endproc
.LFE32:
	.size	main, .-main
	.globl	This
	.bss
	.align 8
	.type	This, @object
	.size	This, 8
This:
	.zero	8
	.ident	"GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4"
	.section	.note.GNU-stack,"",@progbits
修改程式碼如下: 把函式的引數都去掉,保證edi不會被用到。然後最重要的一點。把This的型別轉成unsigned long long。不然彙編程式碼中會符號擴充套件。導致依然報segment fault.
#include <cstdio>

using namespace std;

class tt{
    public:
    /*    void foo(int x) {
            printf("arg: x=%d\n", x);
        }*/

        void foo() {
            printf("_m_s=%d %d\n", _m_s);
        }
        int _m_s;
};

typedef void (tt::* FUNPTR)();
typedef void (*GLOBALPTR)();

template< class ToType, class FromType>
void GetMemberFuncAddr(ToType& addr, FromType from) {
    union{
        FromType _f;
        ToType _t;
    } ut;
    ut._f = from;
    addr = ut._t;
}

unsigned long long This;
int main () {
    tt t;
    t._m_s =123;
    char *ptrc = "hello";
    FUNPTR ptr = &tt::foo;
    (t.*ptr)();
    printf("%x\n", ptr);
    long long p;
    //p = (int)(&tt::foo); 型別不匹配不能強制型別轉換
    GetMemberFuncAddr(p, &tt::foo);
    printf("%x\n", p);

    GLOBALPTR p1 = (GLOBALPTR)p;
//    p1(10000, 'c', ptrc);
    This = (long long)&t;
    __asm__
    (
        "movq This, %rdi \n"
    );
    p1();

    return 0;

彙編程式碼:
<pre name="code" class="cpp">.file	"test_thisPtr.cpp"
	.section	.rodata.str1.1,"aMS",@progbits,1
.LC0:
	.string	"_m_s=%d %d\n"
	.section	.text._ZN2tt3fooEv,"axG",@progbits,_ZN2tt3fooEv,comdat
	.align 2
	.weak	_ZN2tt3fooEv
	.type	_ZN2tt3fooEv, @function
_ZN2tt3fooEv:
.LFB30:
	.cfi_startproc
	subq	$8, %rsp
	.cfi_def_cfa_offset 16
	movl	(%rdi), %edx
	movl	$.LC0, %esi
	movl	$1, %edi
	movl	$0, %eax
	call	__printf_chk
	addq	$8, %rsp
	.cfi_def_cfa_offset 8
	ret
	.cfi_endproc
.LFE30:
	.size	_ZN2tt3fooEv, .-_ZN2tt3fooEv
	.section	.rodata.str1.1
.LC1:
	.string	"%x\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB32:
	.cfi_startproc
	subq	$24, %rsp
	.cfi_def_cfa_offset 32
	movl	$123, (%rsp)
	movq	%rsp, %rdi
	call	_ZN2tt3fooEv
	movl	$_ZN2tt3fooEv, %edx
	movl	$0, %ecx
	movl	$.LC1, %esi
	movl	$1, %edi
	movl	$0, %eax
	call	__printf_chk
	movl	$_ZN2tt3fooEv, %edx
	movl	$.LC1, %esi
	movl	$1, %edi
	movl	$0, %eax
	call	__printf_chk
	movq	%rsp, This(%rip)
#APP
# 49 "test_thisPtr.cpp" 1
	movq This, %rdi

# 0 "" 2
#NO_APP
	call	_ZN2tt3fooEv
	movl	$0, %eax
	addq	$24, %rsp
	.cfi_def_cfa_offset 8
	ret
	.cfi_endproc
.LFE32:
	.size	main, .-main
	.globl	This
	.bss
	.align 8
	.type	This, @object
	.size	This, 8
This:
	.zero	8
	.ident	"GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4"
	.section	.note.GNU-stack,"",@progbits