为啥 sizeof(BaseClass) == sizeof(DerivedClass) 虽然我添加了一个成员

Posted

技术标签:

【中文标题】为啥 sizeof(BaseClass) == sizeof(DerivedClass) 虽然我添加了一个成员【英文标题】:Why is sizeof(BaseClass) == sizeof(DerivedClass) although I add a member为什么 sizeof(BaseClass) == sizeof(DerivedClass) 虽然我添加了一个成员 【发布时间】:2014-01-19 18:25:12 【问题描述】:

来自sizeof(Base) == 24sizeof(Derived) == 24下面的代码。

为什么它们的大小相等?

Base 类有 3 个成员,Derived 类有另一个成员。

class Base

private:
    double d;
protected:
    long l;
public:
    int i;
;

class Derived : public Base

private:
    float f;
;

【问题讨论】:

Base的最后4个字节什么都没有,做Derived可以粘东西吗。 基础:d 为 8 个字节,l 为 8 个字节,i 为 4 个字节,4 个未使用的字节。 阅读packing and byte alignment。 这很奇怪:ideone.com/qF1GIQ -- 为什么是alignof(Base) == 4alignof(double) == 8 @leemes ***.com/q/11825081/1918193 似乎相关。 clang 和 intel 同意 gcc。 【参考方案1】:

碰巧你的类 Base 有 8 字节对齐要求,但它的最后一个成员的大小为 4。这导致在 Base 的内存布局末尾添加一个空填充区域。当您自己实例化 Base 类的对象时,额外的填充会发挥作用,即所谓的最派生对象

Base b; // <- a most-derived object
Base a[10]; // <- an array of most-derived objects

但是,当您将Base 作为基类“嵌入”到类Derived 中时,就不需要在嵌入的Base 子对象的末尾添加额外的填充。

Derived d; // <- object `d` contains an embedded sub-object of type `Base`

智能编译器将尝试重用该区域,方法是将类Derived 的额外字段放入Base 中用于填充的布局区域。在您的情况下,额外字段 Derived::f 偶然具有相同的 4 个字节大小,即它完全适合那里。最终结果是类的总大小没有增加。

一个非常相似(本质上)的效果就是所谓的“空基优化”。在 C++ 中,任何类型的sizeof 都保证大于 0,这意味着空类的sizeof 始终大于零。但是,当您从空基类派生某个其他类时,您可能会观察到基类对派生类的大小的贡献正好为 0 个字节。例如

struct A ;
struct B ;
struct C ;
struct D ;

struct F : A, B, C, D 
  int i;


int main() 
  std::cout << sizeof(A) << std::endl << sizeof(B) << std::endl << 
               sizeof(C) << std::endl << sizeof(D) << std::endl;
  std::cout << sizeof(F) << std::endl;

即使每个基类的sizeof 大于零,sizeof(F) 通常仍会计算为sizeof(int),就好像根本不存在基类子对象一样。

换句话说,正如这些示例所示,基类子对象在内存布局方面比大多数派生对象遵循明显更宽松的规则。这些宽松的规则很容易导致基类的sizeof 只对派生类的sizeof 有部分贡献的情况。

【讨论】:

请注意,重用区域的决定是在 ABI 中完成的,编译器通常没有太多选择。 @Marc Glisse:ABI 是来自不同领域的不同故事。谁说还有 ABI?就语言本身而言,没有 ABI 这样的东西。一个更相关的评论可能是,这些事情实际上可以由用户指定给编译器,用户可以操纵不同的依赖于实现的编译器工具,这些工具允许覆盖默认对齐/打包规则。 对齐要求是如何计算的,即基于什么?它在数据总线大小上吗? @Koushik:这里涉及到很多因素。大多数硬件的典型情况是大小为 N 的基本类型应在第 N 个字节边界处对齐。它要么是硬件要求(如果违反要求,代码将崩溃)或性能指标(如果违反要求,代码将执行更差)。最重要的是,您有自己的更高级别的考虑:缓存性能问题可能使您更喜欢更大的对齐值,内存消耗问题可能使您更喜欢较小的对齐值(如果可用)。以此类推。【参考方案2】:

因为你有 sizeof(double) == sizeof(long) == 8 这通常意味着 alignof(double) 也等于 8。这意味着 Base 必须在 8 字节边界上对齐大小,以防它存储在数组中,并在末尾生成 4 字节填充,Derived 将其删除以放置 f。

【讨论】:

填充与alignof(double) 的相关性比与sizeof(double) 的相关性更高。 @MarcGlisse Editev 更准确,感谢评论【参考方案3】:

用pahole来解决:

class Base 
private:

    double                     d;                    /*     0     8 */
protected:

    long int                   l;                    /*     8     8 */
    int                        i;                    /*    16     4 */


    /* size: 24, cachelines: 1, members: 3 */
    /* padding: 4 */
    /* last cacheline: 24 bytes */
;
class Derived : public Base 
public:

    /* class Base                <ancestor>; */      /*     0    24 */

    /* XXX last struct has 4 bytes of padding */
private:

    /* Bitfield combined with next fields */

    float                      f;                    /*    20     4 */


    /* size: 24, cachelines: 1, members: 2 */
    /* paddings: 1, sum paddings: 4 */
    /* last cacheline: 24 bytes */
;

【讨论】:

【参考方案4】:

由于对齐需要而填充:

虽然编译器(或解释器)通常分配单独的 对齐边界上的数据项,数据结构通常有成员 具有不同的对齐要求。保持正确对齐 翻译器通常会插入额外的未命名数据成员,因此 每个成员都正确对齐。另外数据结构 作为一个整体,可以用最终的未命名成员填充。这允许每个 要正确对齐的结构数组的成员。

在此处了解更多信息:http://en.wikipedia.org/wiki/Data_structure_alignment#Data_structure_padding

【讨论】:

以上是关于为啥 sizeof(BaseClass) == sizeof(DerivedClass) 虽然我添加了一个成员的主要内容,如果未能解决你的问题,请参考以下文章

为啥将 C-Array 传递给函数时 sizeof() 值错误? [复制]

C++“新”内存分配

python:how does subclass call baseclass's __init__()

C# 泛型方法约束为继承自某类时,调用方法,传子类实参,为什么报错?应该怎么写

为啥 sizeof(string) == 32?

为啥 sizeof("-2147483648") - 1