草庐IT

c++ - 编译器关于此指针、虚函数和多重继承的详细信息

coder 2024-02-11 原文

我正在阅读 Bjarne 的论文:Multiple Inheritance for C++ .

在第 3 节第 370 页中,Bjarne 说“编译器将成员函数的调用转换为带有“额外”参数的“普通”函数调用;该“额外”参数是指向成员函数所针对的对象的指针叫做。”

我对这个额外的论点感到困惑。请看以下两个例子:

示例 1 :(第372页)

class A {
    int a;
    virtual void f(int);
    virtual void g(int);
    virtual void h(int);
};
class B : A {int b; void g(int); };
class C : B {int c; void h(int); };

类 c 对象 C 看起来像:

C:
-----------                vtbl:
+0:  vptr -------------->  -----------
+4:  a                     +0: A::f
+8:  b                     +4: B::g
+12: c                     +8: C::h
-----------                -----------  

对虚函数的调用被编译器转换为间接调用。例如,
C* pc;
pc->g(2)

变成这样:
(*(pc->vptr[1]))(pc, 2)

Bjarne的论文告诉了我上述结论。路过this点是 C*。

在下面的例子中,Bjarne 讲述了另一个让我完全困惑的故事!

示例 2 :(第373页)

给定两个类
class A {...};
class B {...};
class C: A, B {...};

类 C 的对象可以像这样布置为连续对象:
pc-->          ----------- 
                  A part
B:bf's this--> -----------  
                  B part
               ----------- 
                  C part
               -----------

给定 C* 调用 B 的成员函数:
C* pc;
pc->bf(2); //assume that bf is a member of B and that C has no member named bf.

Bjarne 写道:“当然,B::bf() 期望 B*(成为它的 this 指针)。”编译器将调用转换为:
bf__F1B((B*)((char*)pc+delta(B)), 2);

为什么这里我们需要一个 B* 指针作为 this ?
如果我们只是传递一个 *C 指针作为 this ,我认为我们仍然可以正确访问 B 的成员。例如,要在 B::bf() 中获取类 B 的成员,我们只需要执行以下操作:*(this+offset)。这个偏移量可以被编译器知道。这是正确的吗?

跟进示例 1 和 2 的问题:

(1)当是线性链式推导时(例1),为什么可以预期C对象和B对象在同一个地址,依次是A子对象?使用C*指针访问示例1中函数B::g内的B类成员没有问题吗?比如我们要访问成员b,运行时会发生什么? *(电脑+8)?

(2)为什么我们可以对多重继承使用相同的内存布局(线性链式推导)?假设在示例 2 中,类 A , B , C具有与示例 1 完全相同的成员。A :int af ; B :int bbf (或称之为 g); C :int ch .为什么不直接使用内存布局,例如:
 -----------               
+0:  a                     
+4:  b                    
+8: c                     
-----------   

(3) 我写了一些简单的代码来测试线性链推导和多重继承之间的区别。
class A {...};
class B : A {...};
class C: B {...};
C* pc = new C();
B* pb = NULL;
pb = (B*)pc;
A* pa = NULL;
pa = (A*)pc;
cout << pc << pb << pa

它表明 pa , pbpc有相同的地址。
class A {...};
class B {...};
class C: A, B {...};
C* pc = new C();
B* pb = NULL;
pb = (B*)pc;
A* pa = NULL;
pa = (A*)pc;

现在,pcpa具有相同的地址,而 pbpa 有一些偏移和 pc .

为什么编译会产生这些差异?

示例 3 :(第377页)
class A {virtual void f();};
class B {virtual void f(); virtual void g();};
class C: A, B {void f();};
A* pa = new C;
B* pb = new C;
C* pc = new C;
pa->f();
pb->f();
pc->f();
pc->g()

(1) 第一个问题是关于pc->g()这与示例 2 中的讨论有关。 compile 是否执行以下转换:
pc->g() ==> g__F1B((*B)((char*)pc+delta(B)))

或者我们必须等待运行时执行此操作?

(2) Bjarne 写道:进入 C::f , this指针必须指向 C 的开头对象(而不是 B 部分)。但是,在编译时通常不知道 Bpb 指出是 C 的一部分所以编译器不能减去常量 delta(B) .

为什么我们不知道 B pb指向的对象是 C 的一部分在编译时?据我了解,B* pb = new C , pb指向一个创建的 C对象和 C继承自 B , 所以一个 B指针 pb 指向 C 的一部分.

(3) 假设我们不知道B指向 pb 的指针是 C 的一部分在编译时。所以我们必须存储实际与 vtbl 一起存储的运行时的 delta(B)。所以 vtbl 条目现在看起来像:
struct vtbl_entry {
    void (*fct)();
    int  delta;
}

比亚内写道:
pb->f() // call of C::f:
register vtbl_entry* vt = &pb->vtbl[index(f)];
(*vt->fct)((B*)((char*)pb+vt->delta)) //vt->delta is a negative number I guess

我在这里完全困惑。为什么 (B*) 在 (*vt->fct)((B*)((char*)pb+vt->delta)) 中不是 (C*) ???根据我的理解和 Bjarne 在 5.1 节第 377 页第一句的介绍,我们应该将 C* 作为 this 传递。这里!!!!!!

紧接着上面的代码片段,Bjarne 继续写:
请注意,对象指针可能需要调整为 po
在查找指向 vtbl 的成员之前,int 到正确的子对象。

天啊!!!我完全不知道 Bjarne 想说什么?你能帮我解释一下吗?

最佳答案

Bjarne wrote: "Naturally, B::bf() expects a B* (to become its this pointer)." The compiler transforms the call into:


bf__F1B((B*)((char*)pc+delta(B)), 2);

Why here we need a B* pointer to be the this?



考虑 B隔离:编译器需要能够编译代码ala B::bf(B* this) .它不知道可以从 B 进一步派生哪些类。 (而且派生代码的引入可能要在 B::bf 编译后很久才会发生)。 B::bf 的代码不会神奇地知道如何将指针从其他类型(例如 C* )转换为 B*它可以用来访问数据成员和运行时类型信息(RTTI/虚拟调度表,类型信息)。

相反,来电有责任提取有效的 B*B涉及任何实际运行时类型的子对象(例如 C )。在这种情况下,C*保存全局的起始地址C可能与 A 的地址匹配的对象子对象,以及 B子对象是一些固定但非 0 的内存偏移量:它是必须添加到 C* 的偏移量(以字节为单位)以获得有效的 B*拨打电话 B::bf - 当指针从 C* 转换时,调整就完成了输入到 B*类型。

(1) When it's a linear chain derivation (example 1), why the C object can be expected to be at the same address as the B and in turn A sub-objects? There is no problem to use a C* pointer to access class B's members inside the function B::g in example 1? For example, we want to access the member b, what will happen in runtime? *(pc+8)?



线性推导 B : A 和 C : B 可以被认为是依次在 A 末尾添加 B 特定字段,然后在 B 末尾添加 C 特定字段(仍然是 B 特定字段添加在 A 末尾)。所以整个事情看起来像:
[[[A fields...]B-specific-fields....]C-specific-fields...]
 ^
 |--- A, B & C all start at the same address

然后,当我们谈论“B”时,我们谈论的是所有嵌入的 A 字段以及添加项,而对于“C”,仍然有所有 A 和 B 字段:它们都从相同的地址开始 .

关于*(pc+8) - 没错(考虑到我们向地址添加了 8 个字节,而不是通常的 C++ 行为,即添加指针大小的倍数)。

(2) Why can we use the same memory layout (linear chain derivation) for the multiple-inheritance? Assuming in example 2, class A, B, C have exactly the same members as the example 1. A: int a and f; B: int b and bf (or call it g); C: int c and h. Why not just use the memory layout like:


-----------               
+0:  a                     
+4:  b                    
+8: c                     
-----------   

没有理由 - 这正是发生的事情......相同的内存布局。不同的是B子对象不考虑A成为自己的一部分。现在是这样的:
[[A fields...][B fields....]C-specific-fields...]
 ^             ^
 \ A&C start   \ B starts

所以当你拨打 B::bf它想知道哪里B对象开始 - this您提供的指针应该在上面列表中的“+4”处;如果您拨打 B::bf使用 C*那么编译器生成的调用代码将需要添加 4 以形成隐式 this参数 B::bf() . B::bf()不能简单地告诉在哪里AC从 +0 开始:B::bf()对这两个类一无所知,也不知道如何到达 b或者它的 RTTI,如果你给它一个指向它自己的 +4 地址以外的任何东西的指针。

关于c++ - 编译器关于此指针、虚函数和多重继承的详细信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30747633/

有关c++ - 编译器关于此指针、虚函数和多重继承的详细信息的更多相关文章

  1. ruby-on-rails - Rails 常用字符串(用于通知和错误信息等) - 2

    大约一年前,我决定确保每个包含非唯一文本的Flash通知都将从模块中的方法中获取文本。我这样做的最初原因是为了避免一遍又一遍地输入相同的字符串。如果我想更改措辞,我可以在一个地方轻松完成,而且一遍又一遍地重复同一件事而出现拼写错误的可能性也会降低。我最终得到的是这样的:moduleMessagesdefformat_error_messages(errors)errors.map{|attribute,message|"Error:#{attribute.to_s.titleize}#{message}."}enddeferror_message_could_not_find(obje

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  4. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  5. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  6. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  7. ruby - Sinatra set cache_control to static files in public folder编译错误 - 2

    我不知道为什么,但是当我设置这个设置时它无法编译设置:static_cache_control,[:public,:max_age=>300]这是我得到的syntaxerror,unexpectedtASSOC,expecting']'(SyntaxError)set:static_cache_control,[:public,:max_age=>300]^我只想将“过期”header设置为css、javaascript和图像文件。谢谢。 最佳答案 我猜您使用的是Ruby1.8.7。Sinatra文档中显示的语法似乎是在Ruby1.

  8. ruby - 在 Ruby 中按名称传递函数 - 2

    如何在Ruby中按名称传递函数?(我使用Ruby才几个小时,所以我还在想办法。)nums=[1,2,3,4]#Thisworks,butismoreverbosethanI'dlikenums.eachdo|i|putsiend#InJS,Icouldjustdosomethinglike:#nums.forEach(console.log)#InF#,itwouldbesomethinglike:#List.iternums(printf"%A")#InRuby,IwishIcoulddosomethinglike:nums.eachputs在Ruby中能不能做到类似的简洁?我可以只

  9. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  10. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

随机推荐