【问题标题】:how to see content of a method pointer?如何查看方法指针的内容?
【发布时间】:2016-04-02 09:58:31
【问题描述】:
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;

我的问题是:

  1. 为什么reinterpret_cast不适合这个场合?

  2. 还有没有办法,可以看到方法指针的内容?

【问题讨论】:

  • 指向成员函数的指针是“实现定义的”,通常比单个值更复杂。
  • 方法指针不一定是地址,也不一定可以表示为整数。
  • @MatsPetersson @molbdnilo sizeof 看到的方法指针的大小与我的情况下的 long long 相同,因此应该有一些方法可以查看事物的位模式(对于缺少更好的词)sizeof 认为是方法指针。
  • 但 sizeof(struct {int, int}) [在 64 位架构中] 也将与 64 位 long long 相同...
  • 我认为您在转换为 char 以外的简单方法时遇到了别名问题。因此,您可以将 &amp;bfunc 转换为 char* 并从那里轻松检查。或者有一些方法可以使用联合。 (reinterpret_cast 执行您最初尝试的操作将是 reinterpret_cast&lt;unsigned long long&amp;&gt;,但我认为这存在混叠问题)。

标签: c++ casting function-pointers member-function-pointers reinterpret-cast


【解决方案1】:

对此有一个非常简单的答案。指向方法的指针是非“普通”指针,不能转换为那些,即使通过reinterpret_cast。可以先投射到void*,然后再投射到long long,但这确实是不明智的。

请记住,指向方法的指针的大小不一定(通常不是!)等于“正常”指针的大小。大多数编译器实现方法指针的方式,它是“普通”指针大小的两倍。

GCC 会抱怨指向 void* 的指向方法的指针以迂腐模式强制转换,但仍会生成代码。

【讨论】:

  • 谢谢,它成功了。我只是想看看方法指针的内部结构,我没有在生产代码中使用它。
  • @q126y,很可能,您不会看到指针的 internals(无论您的意思是什么)。 void* 转换可能会丢失它的重要部分。
  • @SergeyA @tenfour 为什么它会丢失重要的部分?这些位将保持不变,对吗?它只会被解释为void*,不是吗?
  • @q126y,不,这是人们一直试图向你解释的事情。指向方法的指针不是一个简单的指针,就像一个指向函数的指针。
  • @SergeyA 是的,我明白这一点,但是指向方法的指针有一些大小和一些位模式。是的,位模式不代表函数的地址,但应该可以检查与方法指针相关的位。
【解决方案2】:

使用 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

我跳过了一些类型转换和其他东西以使其更简单。

注意 这并不是说“它始终是这样实现的。这是编译器可能会做的事情的一个例子。不同的编译器会以微妙的方式做这件事。但是如果函数可能或可能不是虚拟的,编译器首先必须弄清楚哪个。[在上面的例子中,我很确定我们可以打开优化并获得更好的代码,但它可能只是弄清楚到底发生了什么并删除所有的代码,这对于理解它是如何工作的毫无意义]

【讨论】:

  • 您在回答中的类似输出的程序集叫什么?如何生成它?在in essence 部分,您是如何理解它的,任何书籍、链接都会很棒。
  • 它不是一种正式语言,它通常被称为“伪代码”,在这种情况下基于一种 LLVM-IR 语法,但我只是在进行过程中编造出来的,所以没有书或类似书籍,我只是将 LLVM-IR 翻译成更易于理解的形式来描述这些步骤 - 简化且不那么详细。
猜你喜欢
  • 1970-01-01
  • 2022-10-25
  • 2013-01-29
  • 2017-09-17
  • 2017-05-16
  • 2013-04-08
  • 2013-07-31
  • 2018-01-21
  • 1970-01-01
相关资源
最近更新 更多