在C+中编译虚拟继承类时,Clang如何计算虚拟表中GEP指令的索引+;? 我试图理解Clang在C++中编译虚拟继承类的方式。这是我的密码: // test.cpp #include <stdio.h> int global_obj; int *global_ptr = &global_obj; class A { public: virtual int f(int *i) { return *i; } }; class B: virtual public A { // class B is virtual inheritance class of A }; int main(int argc, char **argv) { int *ptr = &global_obj; B *pb = new B; int a = pb->f(ptr); return a; }

在C+中编译虚拟继承类时,Clang如何计算虚拟表中GEP指令的索引+;? 我试图理解Clang在C++中编译虚拟继承类的方式。这是我的密码: // test.cpp #include <stdio.h> int global_obj; int *global_ptr = &global_obj; class A { public: virtual int f(int *i) { return *i; } }; class B: virtual public A { // class B is virtual inheritance class of A }; int main(int argc, char **argv) { int *ptr = &global_obj; B *pb = new B; int a = pb->f(ptr); return a; },clang,llvm,llvm-ir,Clang,Llvm,Llvm Ir,下面是编译的LLVM位代码,其中\u ZN1BC1Ev和\u ZN1AC2Ev是类B和A的编译构造函数 %class.B = type { %class.A } %class.A = type { i32 (...)** } @global_obj = global i32 0, align 4 @global_ptr = global i32* @global_obj, align 8 @_ZTV1B = linkonce_odr unnamed_addr constant { [5 x i

下面是编译的LLVM位代码,其中
\u ZN1BC1Ev
\u ZN1AC2Ev
是类
B
A
的编译构造函数

%class.B = type { %class.A }
%class.A = type { i32 (...)** }

@global_obj = global i32 0, align 4
@global_ptr = global i32* @global_obj, align 8
@_ZTV1B = linkonce_odr unnamed_addr constant { [5 x i8*] } { [5 x i8*] [i8* null, i8* null, i8* null, i8* null, i8* bitcast (i32 (%class.A*, i32*)* @_ZN1A1fEPi to i8*)] }, align 8
@_ZTT1B = linkonce_odr unnamed_addr constant [2 x i8*] [i8* bitcast (i8** getelementptr inbounds ({ [5 x i8*] }, { [5 x i8*] }* @_ZTV1B, i32 0, inrange i32 0, i32 4) to i8*), i8* bitcast (i8** getelementptr inbounds ({ [5 x i8*] }, { [5 x i8*] }* @_ZTV1B, i32 0, inrange i32 0, i32 4) to i8*)], align 8
@_ZTV1A = linkonce_odr unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%class.A*, i32*)* @_ZN1A1fEPi to i8*)] }, align 8

; Function Attrs: noinline norecurse ssp uwtable
define i32 @main(i32, i8**) #0 {
  %3 = call i8* @_Znwm(i64 8) #3
  %4 = bitcast i8* %3 to %class.B*
  call void @_ZN1BC1Ev(%class.B* %4) #4
  %5 = bitcast %class.B* %4 to i8**
  %6 = load i8*, i8** %5, align 8
  %7 = getelementptr i8, i8* %6, i64 -32
  %8 = bitcast i8* %7 to i64*
  %9 = load i64, i64* %8, align 8
  %10 = bitcast %class.B* %4 to i8*
  %11 = getelementptr inbounds i8, i8* %10, i64 %9
  %12 = bitcast i8* %11 to %class.A*
  %13 = bitcast %class.A* %12 to i32 (%class.A*, i32*)***
  %14 = load i32 (%class.A*, i32*)**, i32 (%class.A*, i32*)*** %13, align 8
  %15 = getelementptr inbounds i32 (%class.A*, i32*)*, i32 (%class.A*, i32*)** %14, i64 0
  %16 = load i32 (%class.A*, i32*)*, i32 (%class.A*, i32*)** %15, align 8
  %17 = call i32 %16(%class.A* %12, i32* @global_obj)
  ret i32 %17
}

; Function Attrs: nobuiltin
declare noalias i8* @_Znwm(i64) #1

; Function Attrs: noinline nounwind ssp uwtable
define linkonce_odr void @_ZN1BC1Ev(%class.B*) unnamed_addr #2 align 2 {
  %2 = bitcast %class.B* %0 to %class.A*
  call void @_ZN1AC2Ev(%class.A* %2) #4
  %3 = bitcast %class.B* %0 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ({ [5 x i8*] }, { [5 x i8*] }* @_ZTV1B, i32 0, inrange i32 0, i32 4) to i32 (...)**), i32 (...)*** %3, align 8
  %4 = bitcast %class.B* %0 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ({ [5 x i8*] }, { [5 x i8*] }* @_ZTV1B, i32 0, inrange i32 0, i32 4) to i32 (...)**), i32 (...)*** %4, align 8
  ret void
}

; Function Attrs: noinline nounwind ssp uwtable
define linkonce_odr void @_ZN1AC2Ev(%class.A*) unnamed_addr #2 align 2 {
  %2 = bitcast %class.A* %0 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ({ [3 x i8*] }, { [3 x i8*] }* @_ZTV1A, i32 0, inrange i32 0, i32 2) to i32 (...)**), i32 (...)*** %2, align 8
  ret void
}

; Function Attrs: noinline nounwind ssp uwtable
define linkonce_odr i32 @_ZN1A1fEPi(%class.A*, i32*) unnamed_addr #2 align 2 {
  %3 = load i32, i32* %1, align 4
  ret i32 %3
}
我知道Clang将引入一个虚拟表来捕获类a和B的对象

但是当深入研究主函数的编译时,我真的不明白为什么Clang在主函数中引入了索引
-32
GEP

在下一个
GEP
中,索引
%9
的值是多少。为什么不能在编译时确定

  %7 = getelementptr i8, i8* %6, i64 -32
  %8 = bitcast i8* %7 to i64*
  %9 = load i64, i64* %8, align 8
  %10 = bitcast %class.B* %4 to i8*
  %11 = getelementptr inbounds i8, i8* %10, i64 %9
有人知道为什么叮当这样做吗


非常感谢你阅读我很长的问题

编译时无法确定虚拟继承基类的位置,并将延迟到运行时。虚拟基偏移量(vbase偏移量)位于vtable内,因此首先代码加载vtable指针:

  %5 = bitcast %class.B* %4 to i8**
  %6 = load i8*, i8** %5, align 8
然后加载vbase偏移量(从
vptr-32的预定义位置开始):

这用于计算到基类的偏移量:

  %10 = bitcast %class.B* %4 to i8*
  %11 = getelementptr inbounds i8, i8* %10, i64 %9
  %12 = bitcast i8* %11 to %class.A*
并从基类的vtable加载指向虚拟方法的指针:

  %13 = bitcast %class.A* %12 to i32 (%class.A*, i32*)***
  %14 = load i32 (%class.A*, i32*)**, i32 (%class.A*, i32*)*** %13, align 8
  %15 = getelementptr inbounds i32 (%class.A*, i32*)*, i32 (%class.A*, i32*)** %14, i64 0
  %16 = load i32 (%class.A*, i32*)*, i32 (%class.A*, i32*)** %15, align 8
最后称之为:

  %17 = call i32 %16(%class.A* %12, i32* @global_obj)

您可以找到更多有关vtables的组织方式的详细信息(但请注意,这不是为胆小的人准备的)。

谢谢您的解释!
  %13 = bitcast %class.A* %12 to i32 (%class.A*, i32*)***
  %14 = load i32 (%class.A*, i32*)**, i32 (%class.A*, i32*)*** %13, align 8
  %15 = getelementptr inbounds i32 (%class.A*, i32*)*, i32 (%class.A*, i32*)** %14, i64 0
  %16 = load i32 (%class.A*, i32*)*, i32 (%class.A*, i32*)** %15, align 8
  %17 = call i32 %16(%class.A* %12, i32* @global_obj)