使用虚拟继承的类似乎允许基类构造函数覆盖另一个基类的成员

时间:2019-01-09 03:35:09

标签: c++ gcc clang virtual-inheritance memory-layout

我对包含虚拟基础的对象的内存布局还不够熟悉,无法理解为什么clang和gcc都错误地编译了以下内容。这是一项学术活动,因此请原谅memset()在构造函数中的轻浮性。我正在同时使用clang 7和gcc 8.2的Linux x86-64进行测试:

#include <cstring>

struct A {
    A() { memset(this, 0, sizeof(A)); }

    int i;
    char a;
};

struct B { char b = 'b'; };
struct C : virtual B, A {};

char foo() {
    C c;
    return c.b;
}

当使用-O2 -Wall -pedantic -std=c++17进行编译时,两个编译器都将生成以下程序集,而不会发出警告:

foo():
    xor     eax, eax
    ret

在调用memset的过程中更改C使其不虚拟继承B或将sizeof(A)更改为5或更少,都将编译器的输出更改为返回{{1} },正如我期望的那样:

'b'

foo(): mov al, 98 # gcc uses eax directly, here ret 的虚拟/非虚拟的C的内存布局是什么?这些编译器通过允许B的构造函数将a的成员归零来构造错误吗?不同的基层?我知道布局不是由标准定义的,但是我希望所有实现都可以确保类的构造函数不会干扰不相关类的数据成员,即使在这样的多重继承中使用时也是如此。或者至少警告说可能会发生这种情况。 (此处未诊断出gcc的新A警告)。

如果归结为-Wclass-memaccess在构造函数中无效,那么我希望编译器无法编译或至少发出警告。

链接:https://godbolt.org/z/OSQV1j

1 个答案:

答案 0 :(得分:-1)

  

我对包含虚拟碱基的对象的内存布局还不够熟悉

虚拟基础(以及具有虚拟基础的基类子对象)通常不会被构造或表示为相同类型的完整对象,与其他具有相同布局的子对象(该基类子对象的每个子对象的相对位置)作为具有相同类型的完整对象:

  • 数组元素
  • 班级成员
  • 没有虚拟基类的非虚拟基类子对象

它们都是像一个完整的对象一样构造和表示的。

说明:尽管基类子对象的构造函数通常与完整对象相同,但专门为基类子对象保留的内存可能小于其正常大小

>
  

了解为什么clang和gcc无法正确编译以下内容。

您还没有发布任何错误代码生成的证据。

  

这是一项学术练习,请原谅轻浮

这不是轻浮的,是明显错误

  

memset()在副本构造函数中。

这样做会覆盖对象以破坏对象。

代码使用了不受支持的操作(在构造期间覆盖c2对象的内存),并且编译器没有警告您您的代码使用的对象的生存期以调用低级内存访问功能(memset)。在基类的构造函数中终止生命周期是非法的:从技术上讲,生命周期甚至在您结束时就没有开始。

如果要通过覆盖来结束对象的生命周期,请在构造之后进行。

摘要:

不能保证每个T类型的子对象都“拥有” sizeof (T)个字节,并且可以覆盖这些子对象;但是,这对于数组元素和成员是可以保证的。