我正在使用C ++14§3.11/ 2中的示例:
struct B { long double d; };
struct D : virtual B { char c; }
在clang,g ++和VS2015
中运行下面的代码段#include <iostream>
struct B { long double d; };
struct D : /*virtual*/ B { char c; };
int main()
{
std::cout << "sizeof(long double) = " << sizeof(long double) << '\n';
std::cout << "alignof(long double) = " << alignof(long double) << '\n';
std::cout << "sizeof(B) = " << sizeof(B) << '\n';
std::cout << "alignof(B) = " << alignof(B) << '\n';
std::cout << "sizeof(D) = " << sizeof(D) << '\n';
std::cout << "alignof(D) = " << alignof(D) << '\n';
}
我得到了以下结果:
clang g++ VS2015
sizeof(long double) 16 16 8
alignof(long double) 16 16 8
sizeof(B) 16 16 8
alignof(B) 16 16 8
sizeof(D) 32 32 16
alignof(D) 16 16 8
现在,在上面代码中的virtual
定义中取消注释struct D
并再次为clang,g ++和VS2015运行代码之后,我获得了以下结果:
clang g++ VS2015
sizeof(long double) 16 16 8
alignof(long double) 16 16 8
sizeof(B) 16 16 8
alignof(B) 16 16 8
sizeof(D) 32 32 24
alignof(D) 16 16 8
我对上面得到的结果毫不怀疑,只有一个例外:为什么VS2015中sizeof(D)
从16增加到24?
我知道这是实现定义的,但是对于这种增加的大小可能有一个合理的解释。如果可能的话,这就是我想知道的。
答案 0 :(得分:1)
如果您实际使用虚拟继承的虚拟方面,我认为对vtable指针的需求变得清晰。 vtable中的一个项目可能是B
从D
开始的偏移量。
假设E
几乎从B
和F
继承E
和D
继承,D
F
B
最终使用E
内的D
作为其基类。在F
的方法中,B
不知道它是graphicalDisplayJFrame.getContentPane().setLayout(new LayerLayout(layerHandler));//graphicalDisplayJFrame is JFrame which represents the display
的基类怎么能找到 graphicalDisplayJPanel = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g2d = (Graphics2D) g;
g2d.rotate(Math.PI, anchorx, anchory); //rotation 180°
}
@Override
public void paintChildren(Graphics g) {
super.paintChildren(g);
Graphics2D g2d2 = (Graphics2D) g;
g2d2.rotate(Math.PI, anchorx, anchory);
}
};
graphicalDisplayJFrame = new JFrame(); //creation of JFrame
graphicalDisplayJFrame.setContentPane(graphicalDisplayJPanel); //setting JFrame to apply rotation
的成员而没有存储在vtable中的信息?
所以clang和G ++将8个字节的填充更改为vtable指针,你认为没有变化。但是VS2015从来没有填充,所以需要为vtable指针添加8个字节。
也许编译器注意到vtable指针的唯一用途是用于计算基指针的低效方案。所以也许这可以优化为简单地使用基指针而不是vtable指针。但这不会改变对8字节的需求。
答案 1 :(得分:0)
当存在virtual
基础对象时,基础对象相对于派生对象地址的位置不是静态可预测的。值得注意的是,如果您稍微扩展类层次结构,很明显可能有多个D
子对象仍需要引用一个B
基础对象:
class I1: public D {};
class I2: public D {};
class Most: public I1, public I2 {};
您可以通过首先转换为D*
或首先转换为Most
来从I1
对象获取I2
:
Most m;
D* d1 = static_cast<I1*>(&m);
D* d2 = static_cast<I2*>(&m);
您将拥有d1 != d2
,即真正有两个D
子对象,但static_cast<B*>(d1) == static_cast<B*>(d2)
,即只有一个B
子对象。要确定如何调整d1
和d2
以查找指向B
子对象的指针,则需要动态偏移。有关如何确定此偏移量的信息需要存储在某处。此信息的存储可能是额外8个字节的来源。
我不认为MSVC ++中类型的对象布局是[公开]记录的,即,无法确定它们正在做什么。从它的外观来看,它们嵌入了一个64位对象,以便能够分辨出基础对象相对于派生对象的地址所处的位置(指向某些类型信息的指针,指向基础的指针,对基础的偏移,某些东西)像那样)。另外8个字节最有可能源于需要存储char
加上一些填充以使对象在合适的边界处对齐。这似乎与其他两个编译器所做的类似,只是它们使用16个字节开始long double
(可能只有10个字节填充到合适的对齐方式)。
要了解C ++对象模型的工作原理,您可能需要查看Stan Lippman的"Inside the C++ Object Model"。它有点过时,但描述了潜在的实现技术。 MSVC ++是否使用其中任何一个我不知道,但它提供了可能使用的想法。
对于gcc和clang使用的对象模型,您可以查看Itanium ABI:它们主要使用Itanium ABI,并对实际使用的CPU进行微调。 / p>
答案 2 :(得分:0)
在visual studio中,默认行为是所有结构都沿着8字节边界对齐。即使你做了
struct A {
char c;
}
然后检查sizeof(A)
,您将看到它是8个字节。
现在,在您的情况下,当您将struct D的继承类型更改为虚拟时,编译器必须执行额外的操作才能完成此操作。首先,它为结构D创建一个虚拟表。这个vtable包含什么?它包含一个指向内存中struct B偏移的单个指针。接下来,它在struct D的头部添加一个vptr,指向新创建的vtable。
因此,现在结构D应该看起来像:
struct D : virtual B { void* vptr; char c; }
所以,D的大小将是:
sizeof (long double) + sizeof (void*) + sizeof (char) = 8 + 8 + 1 = 17
这是我们在开始时讨论的边界对齐的地方。由于所有结构必须与8字节边界对齐而结构D只有17个字节,因此编译器将7个填充字节添加到结构中以使其对齐到8字节边界。
所以现在大小变成:
Size of D = Size of elements of D + Padding bytes for byte alignment = 17 + 7 = 24 bytes.