最近,我一直在阅读“在c ++对象模型内部”。它说,如果要将基类分配给派生类,则还应该将基类中使用的填充复制到派生类中。因此,我在64位计算机上运行测试:
class A {
public:
int valA;
char a;
};
class B : public A {
public:
char b;
};
class C : public B {
public:
char c;
};
int main(){
std::cout << sizeof(A) << " " << sizeof(B) << " " << sizeof(C)
<< std::endl;
C c;
printf("%p\n%p\n%p\n",&c,&c.b,&c.c);
}
这是结果:
8 12 12
0x7ffd22c5072c
0x7ffd22c50734
0x7ffd22c50735
那么C为什么和B一样大小?尽管似乎B在A中使用了3个字节的填充。
答案 0 :(得分:2)
那么C为什么和B一样大小?
因为B
已重用了C::b
的尾随填充。因为B
不是POD(普通旧数据)类(因为它不是标准布局类),所以可以重复使用填充。
尽管B似乎在A中使用了3个字节的填充。
A
的填充不能再用于B
的其他子对象,因为A
是标准布局类,并且可以复制,即A
是POD类
基类的填充是否会复制到派生类中?
我想您不是要询问复制,而是询问派生类的基类子对象是否具有与单个类型相同的填充。
答案可能是从上面得出的:填充将是相同的,除了可以将尾部填充重新用于其他子对象,除非基类是POD,在这种情况下其填充不能可以重复使用。
在可以重复使用填充的情况下,标准未指定是否填充,并且编译器之间存在差异。
请解释或链接到“标准布局类型”的定义。
当前标准草案:
[基本类型]
...标量类型,标准布局类类型([class.prop]),此类类型的数组以及这些类型的cv限定版本统称为标准布局类型。
[class.prop](在标准的较旧版本中,可以直接在[class]下找到它们)
如果满足以下条件,则S类为标准布局类:
(3.1)没有非标准布局类(或此类数组)或引用的非静态数据成员,
(3.2)没有虚函数,也没有虚基类,
(3.3)对所有非静态数据成员具有相同的访问控制,
(3.4)没有非标准布局的基类,
(3.5)最多具有一个任何给定类型的基类子对象,
(3.6)具有该类中的所有非静态数据成员和位字段,以及在同一类中首先声明的基类,并且
(3.7)没有类型集合M(S)的元素作为基类,对于任何类型X,M(X)的定义如下。107[注意:M(X)为的 可能位于某个位置的所有非基类子对象的类型的集合 X中的零偏移。—注释]
(3.7.1)如果X是不包含(可能是继承的)非静态数据成员的非联合类类型,则集合M(X)为空。
(3.7.2)如果X是具有0大小或第一个零大小的X0类型的非静态数据成员的非联合类类型 X的非静态数据成员(其中所述成员可以是匿名的 联合),则集合M(X)由X0和M(X0)的元素组成。
(3.7.3)如果X是联合类型,则集合M(X)是所有M(Ui)和包含所有Ui的集合的联合,其中每个Ui是集合的类型。 X的第ith个非静态数据成员。
(3.7.4)如果X是元素类型为Xe的数组类型,则集合M(X)由Xe和M(Xe)的元素组成。
(3.7.5)如果X是非类,非数组类型,则集合M(X)为空。
项目(3.6)在这种情况下适用。 B的某些成员未在B中首先声明。特别是B :: A :: valA和B :: A :: a首先在A中声明。一种更友好的描述规则的方法是:该类必须具有没有直接成员,或者其祖先都没有成员。在这种情况下,基类和派生类都具有成员,因此它不是标准布局。
答案 1 :(得分:0)
C
与B
的大小相同,因为ABI在平台上选择使用B
中的填充来存储1字节成员C::c
。 B
的末尾有3个字节的填充,因为整个B
对象的对齐方式为4(由于int
中的A
成员)。
B
与A
的大小不同,因为在这种情况下,ABI显然不允许将B::b
存储在A
的填充中是房间。当所有A
成员都公开时,就会发生这种情况,如您的示例所示:如果将任何成员设为私有,则A
,B
和C
的大小将全部是8。我相信这可能是为了ABI向后兼容,而不是出于标准中任何语言的推动。
我不知道标准中是否有直接通过 允许的语言(但不需要这样做),但是显然这种填充重复使用是在继承的情况下。例如,std::memcpy的文档说:
如果对象可能重叠或不可TriviallyCopyable,则未指定memcpy的行为,并且可能未定义。
子对象可能重叠(如果存在)
- 基类子对象,或
- 使用[[no_unique_address]]属性声明的非静态数据成员。
第二个条件仅适用于C ++ 20。
这似乎是为了允许共享填充而编写的:如果此子句不存在,指向memcpy
的{{1}}子类的指针上的B
将会覆盖C
存储在通常用于C::c
的填充中。