如何查看方法指针的内容?
how to see content of a method pointer?
typedef int (D::*fptr)(void);
fptr bfunc;
bfunc=&D::Bfunc;
cout<<(reinterpret_cast<unsigned long long>(bfunc)&0xffffffff00000000)<<endl;
完整代码位于:https://ideone.com/wRVyTu
我正在尝试使用 reinterpret_cast
,但编译器抛出错误
prog.cpp: In function 'int main()': prog.cpp:49:51: error: invalid cast from type 'fptr {aka int (D::*)()}' to type 'long long unsigned int' cout<<(reinterpret_cast<unsigned long long>(bfunc)&0xffffffff00000000)<<endl;
我的问题是:
为什么reinterpret_cast
不适合这个场合?
有没有别的办法,我可以看到方法指针的内容?
对此有一个非常简单的答案。指向方法的指针是 not 'normal' 指针,即使通过 reinterpret_cast
也不能转换为这些指针。可以先转换为 void*
,然后再转换为 long long
,但这确实是不明智的。
记住,指向方法的指针的大小不一定(通常 不是 !)等于 'normal' 指针的大小。大多数编译器实现指向方法的指针的方式,它是 'normal' 指针大小的两倍。
GCC 将抱怨指向 void*
的方法指针以迂腐模式投射,但仍会生成代码。
使用 clang++ 编译你的代码的一个稍微修改的版本(删除了所有 cout
以免得到数千行......),我们得到这个 main
:
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%bfunc = alloca { i64, i64 }, align 8
%dfunc = alloca { i64, i64 }, align 8
store i32 0, i32* %retval, align 4
store { i64, i64 } { i64 1, i64 16 }, { i64, i64 }* %bfunc, align 8
store { i64, i64 } { i64 9, i64 0 }, { i64, i64 }* %dfunc, align 8
ret i32 0
}
请注意,bfunc
和 dfunc
是两个 64 位整数值。如果我为 32 位 x86 编译它是两个 i32
(所以 32 位整数值)。
所以,如果我们让 main
看起来像这样:
int main() {
// your code goes here
typedef int (D::*fptr)(void);
fptr bfunc;
fptr dfunc;
bfunc=&D::Bfunc;
dfunc=&D::Dfunc;
D d;
(d.*bfunc)();
return 0;
}
生成的代码如下所示:
; Function Attrs: norecurse uwtable
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%bfunc = alloca { i64, i64 }, align 8
%dfunc = alloca { i64, i64 }, align 8
%d = alloca %class.D, align 8
store i32 0, i32* %retval, align 4
store { i64, i64 } { i64 1, i64 16 }, { i64, i64 }* %bfunc, align 8
store { i64, i64 } { i64 9, i64 0 }, { i64, i64 }* %dfunc, align 8
call void @_ZN1DC2Ev(%class.D* %d) #3
%0 = load { i64, i64 }, { i64, i64 }* %bfunc, align 8
%memptr.adj = extractvalue { i64, i64 } %0, 1
%1 = bitcast %class.D* %d to i8*
%2 = getelementptr inbounds i8, i8* %1, i64 %memptr.adj
%this.adjusted = bitcast i8* %2 to %class.D*
%memptr.ptr = extractvalue { i64, i64 } %0, 0
%3 = and i64 %memptr.ptr, 1
%memptr.isvirtual = icmp ne i64 %3, 0
br i1 %memptr.isvirtual, label %memptr.virtual, label %memptr.nonvirtual
memptr.virtual: ; preds = %entry
%4 = bitcast %class.D* %this.adjusted to i8**
%vtable = load i8*, i8** %4, align 8
%5 = sub i64 %memptr.ptr, 1
%6 = getelementptr i8, i8* %vtable, i64 %5
%7 = bitcast i8* %6 to i32 (%class.D*)**
%memptr.virtualfn = load i32 (%class.D*)*, i32 (%class.D*)** %7, align 8
br label %memptr.end
memptr.nonvirtual: ; preds = %entry
%memptr.nonvirtualfn = inttoptr i64 %memptr.ptr to i32 (%class.D*)*
br label %memptr.end
memptr.end: ; preds = %memptr.nonvirtual, %memptr.virtual
%8 = phi i32 (%class.D*)* [ %memptr.virtualfn, %memptr.virtual ], [ %memptr.nonvirtualfn, %memptr.nonvirtual ]
%call = call i32 %8(%class.D* %this.adjusted)
ret i32 0
}
这并不完全是微不足道的,但在本质上:
%memptr.adj = Read adjustment from bfunc[1]
%2 = %d[%memptr.adj]
cast %2 to D*
%memptr.ptr = bfunc[0]
if (%memptr.ptr & 1) goto is_virtual else goto is_non_virtual
is_virtual:
%memptr.virtual=vtable[%memptr.ptr-1]
goto common
is_non_virtual:
%memptr.non_virtual = %memptr.ptr
common:
if we came from
is_non_virtual: %8 = %memptr.non_virtual
is_virtual: %8 = %memptr.virutal
call %8
我跳过了一些类型转换和其他内容以使其更简单。
注意 这并不是说“这就是它总是如何实现的。这是编译器可能做的事情的一个例子。不同的编译器会以不同的方式做到这一点。但是如果函数可能是也可能不是虚函数,编译器首先必须弄清楚是哪个。[在上面的例子中,我相当确定我们可以打开优化并获得更好的代码,但它大概只会弄清楚到底是什么继续并删除所有代码,这对于理解其工作原理毫无意义]
typedef int (D::*fptr)(void);
fptr bfunc;
bfunc=&D::Bfunc;
cout<<(reinterpret_cast<unsigned long long>(bfunc)&0xffffffff00000000)<<endl;
完整代码位于:https://ideone.com/wRVyTu
我正在尝试使用 reinterpret_cast
,但编译器抛出错误
prog.cpp: In function 'int main()': prog.cpp:49:51: error: invalid cast from type 'fptr {aka int (D::*)()}' to type 'long long unsigned int' cout<<(reinterpret_cast<unsigned long long>(bfunc)&0xffffffff00000000)<<endl;
我的问题是:
为什么
reinterpret_cast
不适合这个场合?有没有别的办法,我可以看到方法指针的内容?
对此有一个非常简单的答案。指向方法的指针是 not 'normal' 指针,即使通过 reinterpret_cast
也不能转换为这些指针。可以先转换为 void*
,然后再转换为 long long
,但这确实是不明智的。
记住,指向方法的指针的大小不一定(通常 不是 !)等于 'normal' 指针的大小。大多数编译器实现指向方法的指针的方式,它是 'normal' 指针大小的两倍。
GCC 将抱怨指向 void*
的方法指针以迂腐模式投射,但仍会生成代码。
使用 clang++ 编译你的代码的一个稍微修改的版本(删除了所有 cout
以免得到数千行......),我们得到这个 main
:
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%bfunc = alloca { i64, i64 }, align 8
%dfunc = alloca { i64, i64 }, align 8
store i32 0, i32* %retval, align 4
store { i64, i64 } { i64 1, i64 16 }, { i64, i64 }* %bfunc, align 8
store { i64, i64 } { i64 9, i64 0 }, { i64, i64 }* %dfunc, align 8
ret i32 0
}
请注意,bfunc
和 dfunc
是两个 64 位整数值。如果我为 32 位 x86 编译它是两个 i32
(所以 32 位整数值)。
所以,如果我们让 main
看起来像这样:
int main() {
// your code goes here
typedef int (D::*fptr)(void);
fptr bfunc;
fptr dfunc;
bfunc=&D::Bfunc;
dfunc=&D::Dfunc;
D d;
(d.*bfunc)();
return 0;
}
生成的代码如下所示:
; Function Attrs: norecurse uwtable
define i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%bfunc = alloca { i64, i64 }, align 8
%dfunc = alloca { i64, i64 }, align 8
%d = alloca %class.D, align 8
store i32 0, i32* %retval, align 4
store { i64, i64 } { i64 1, i64 16 }, { i64, i64 }* %bfunc, align 8
store { i64, i64 } { i64 9, i64 0 }, { i64, i64 }* %dfunc, align 8
call void @_ZN1DC2Ev(%class.D* %d) #3
%0 = load { i64, i64 }, { i64, i64 }* %bfunc, align 8
%memptr.adj = extractvalue { i64, i64 } %0, 1
%1 = bitcast %class.D* %d to i8*
%2 = getelementptr inbounds i8, i8* %1, i64 %memptr.adj
%this.adjusted = bitcast i8* %2 to %class.D*
%memptr.ptr = extractvalue { i64, i64 } %0, 0
%3 = and i64 %memptr.ptr, 1
%memptr.isvirtual = icmp ne i64 %3, 0
br i1 %memptr.isvirtual, label %memptr.virtual, label %memptr.nonvirtual
memptr.virtual: ; preds = %entry
%4 = bitcast %class.D* %this.adjusted to i8**
%vtable = load i8*, i8** %4, align 8
%5 = sub i64 %memptr.ptr, 1
%6 = getelementptr i8, i8* %vtable, i64 %5
%7 = bitcast i8* %6 to i32 (%class.D*)**
%memptr.virtualfn = load i32 (%class.D*)*, i32 (%class.D*)** %7, align 8
br label %memptr.end
memptr.nonvirtual: ; preds = %entry
%memptr.nonvirtualfn = inttoptr i64 %memptr.ptr to i32 (%class.D*)*
br label %memptr.end
memptr.end: ; preds = %memptr.nonvirtual, %memptr.virtual
%8 = phi i32 (%class.D*)* [ %memptr.virtualfn, %memptr.virtual ], [ %memptr.nonvirtualfn, %memptr.nonvirtual ]
%call = call i32 %8(%class.D* %this.adjusted)
ret i32 0
}
这并不完全是微不足道的,但在本质上:
%memptr.adj = Read adjustment from bfunc[1]
%2 = %d[%memptr.adj]
cast %2 to D*
%memptr.ptr = bfunc[0]
if (%memptr.ptr & 1) goto is_virtual else goto is_non_virtual
is_virtual:
%memptr.virtual=vtable[%memptr.ptr-1]
goto common
is_non_virtual:
%memptr.non_virtual = %memptr.ptr
common:
if we came from
is_non_virtual: %8 = %memptr.non_virtual
is_virtual: %8 = %memptr.virutal
call %8
我跳过了一些类型转换和其他内容以使其更简单。
注意 这并不是说“这就是它总是如何实现的。这是编译器可能做的事情的一个例子。不同的编译器会以不同的方式做到这一点。但是如果函数可能是也可能不是虚函数,编译器首先必须弄清楚是哪个。[在上面的例子中,我相当确定我们可以打开优化并获得更好的代码,但它大概只会弄清楚到底是什么继续并删除所有代码,这对于理解其工作原理毫无意义]