【问题标题】:C++ object model by gcc 4.4.7: base class subobject padding occupied in derived class objectgcc 4.4.7的C ++对象模型:派生类对象中占用的基类子对象填充
【发布时间】:2016-05-31 12:18:34
【问题描述】:

我正在阅读——3.4 继承和数据成员。它说“派生类中基类子对象完整性的语言保证”。还举了一个例子:

class Concrete1
{
private:
    int val;
    char bit1;
};

class Concrete2 : public Concrete1
{
private:
    char bit2;
};

class Concrete3 : public Concrete2
{
private:
    char bit3;
};

Concrete1 对象模型是:

4 bytes for val, 
1 byte for bit1,
3 bytes padding. 
// 8 bytes in total.

Concrete2 对象模型是:

8 bytes for Concrete1 subobject, 
1 byte for bit2,
3 bytes padding. 
// 12 bytes in total. 

同样,Concrete3 对象模型是 16 字节。

Concrete2 对象成员 bit2 不使用 Concrete1 子对象的填充部分,所以它是 12 字节。但是当我在 gcc 4.4.7 中尝试示例时,Concrete2 对象和 Concrete3 对象与 Concrete1 对象的大小相同——8 字节。所以我猜gcc使用Concrete1对象的填充部分来存储bit2和bit3。我将它们简称为“不使用填充”方式和“使用填充”方式。

为了说明为什么不使用padding部分,书中给出如下代码:

Concrete2 *pc2; 
Concrete1 *pc1_1, *pc2_2; 
pc1_1 = pc2;
*pc1_1 = *pc2_2; // pc1_1 may point to a Concrete2 or Concrete3 object. 

以“不使用填充”的方式,pc2_2指向的Concrete1对象将被复制到pc1_1指向的Concrete2/Concrete3对象的Concrete1子对象中。这本书还说它是一个“成员”副本,但它看起来更像是一个“对象”副本,因为它暗示填充部分也被复制了。

在“使用填充”的方式中,书上说它将覆盖 bit2 成员,因为 *pc2_2 的相应字节是填充字节。同样,我用 gcc 4.4.7 尝试过,结果发现 bit2 成员没有被覆盖。所以我猜这个副本是一个真正的“成员”副本,只是 val 和 bit1 被复制了。

所以我的问题是: 1.我对gcc活动是否正确:“使用填充”方式和真正的“会员”副本? 2.基类的私有成员不能在派生类中访问,但派生类对象在对象模型中包含其基类子对象(val和bit1)中的所有私有成员。为什么它被设计为包含基类私有成员,即使它们甚至不能在派生类对象中访问?仅适用于 *pc1_1 = *pc2_2; 之类的复制操作?

谢谢

【问题讨论】:

  • #2:通常,一个类将具有公共成员函数,而这些函数又使用私有数据成员。即使派生类无法访问其基类的私有成员,它当然也可以调用其公共方法。
  • 您想阅读 C++ Itanium ABI,它描述了 gcc 使用的(部分)布局。特别说明,如果将Concrete1中的private:替换为public:,则派生类的大小会发生变化。

标签: c++ object inheritance gcc padding


【解决方案1】:

关于你的第一个问题:

我尝试使用不同的编译器在 ideone.com 上重现您描述为“使用填充”的行为,但失败了。 Clang、gcc 4.3 和 gcc 5.1 似乎没有使用基类的填充。但是从this Question 的答案可以看出,gcc 确实使用了这种填充。

标准引入了POD-types的概念,但明确说明

9 类 [类]
(...)
7 A 类 S 是标准布局类,如果它: (...)
(7.6) 具有该类及其基类中的所有非静态数据成员和位字段,首先在同一类中声明 (...)

所以这不适用于您的类,因为您在基类和派生类中都有数据成员。我找不到任何关于如何布局非 POD 类型的参考资料。链接问题中的一个答案指出,这已经发生了变化。

关于继承的重要一点是——使用指针或引用——您可以使用派生对象,就好像它是基类的对象一样。这意味着您可以像使用Concrete1* 一样使用Concrete3*。但是不管你是否使用padding这都是独立的,只要你在添加Concrete2和Concrete3的数据成员时不改变Concrete1的布局即可。


关于你的第二个问题:

标准规定:

11 会员访问控制 [class.access]
1 一个类的成员可以是

  • (1.1) 私有;也就是说,它的名称只能被声明它的类的成员和朋友使用。
  • (1.2) 受保护;也就是说,它的名称只能由声明它的类的成员和朋友、从该类派生的类以及它们的朋友使用(参见 11.4)。
  • (1.3) 公开;也就是说,它的名称可以在任何地方使用而不受访问限制。

如您所见,它只指定了名称的使用位置。例如。您仍然可以返回指向私有成员的引用或指针。这意味着它们必须存在于内存中的某个地方。事实上,对象的状态是由其成员的状态定义的 - 私有或公共只是您可以在代码中的哪个位置直接访问它们的问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-11
    • 2011-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-20
    相关资源
    最近更新 更多