【问题标题】:Should an empty base class affect the layout of the derived class?空基类是否会影响派生类的布局?
【发布时间】:2011-12-07 07:03:38
【问题描述】:

C++ 标准(引自草案 n3242)对子对象 [intro.object] 有如下说明:

除非对象是位域或零基类子对象 大小,该对象的地址是它的第一个字节的地址 占据。既不是位域也不是基础的两个不同对象 大小为零的类子对象应具有不同的地址。

现在,给定以下 sn-p:

struct empty { };
struct member: empty { };
struct derived: empty { member m; };

int main(void)
{
    printf("%d", sizeof(derived));
    return 0;
}

gcc 我相信会打印出2,而Visual C++ 2010 会打印出1。我怀疑 gcc 采用标准意味着如果类型的存储代表不同的对象,则不能为它们的存储设置别名。我敢打赌,MSVC 采用的标准是,如果一个子对象的大小为零,你可以做任何你想做的事情。

这是未指定的行为吗?

【问题讨论】:

  • 哪个版本的 gcc?当member 设置为char 时,gcc 4.7 在此处输出 1。
  • @envu,另一个SDK指定的版本。
  • 如果成员有不同的类型(空和字符),他们可以有相同的地址。如果它们属于同一类型,则不能,因为这会破坏对象身份的测试,例如 if (this != &that)
  • 我认为 Bo Persson 是对的。据我所知,VC 的行为不符合规定。 N3290 10/8 和 C++03 10/7 说:two subobjects that have the same class type and that belong to the same most derived object must not be allocated at the same address
  • @BoPersson,你能回答一下吗?鉴于 10/8 的措辞,我认为这是最恰当的解释。

标签: c++ standards unspecified-behavior


【解决方案1】:

这取决于实现。

标准明确允许空基优化,但不要求它。事实上,该标准对内存中类的布局没有太多要求,只是某些类将相互布局兼容(但不是常见的布局)。还指定了成员的顺序(当没有介入的可访问性说明符时),但允许使用填充、页眉、页脚和各种奇怪的东西。

【讨论】:

    【解决方案2】:

    在 C++11 标准的最终版本中,该段落被修改为:

    除非对象是位域或零基类子对象 大小,该对象的地址是它的第一个字节的地址 占据。不是位域的两个对象可能具有相同的 如果一个是另一个的子对象或至少一个是 零大小的基类子对象,它们属于不同的类型; 否则,它们将具有不同的地址。

    虽然我不确定我是否理解这与对象的大小有什么关系。

    【讨论】:

    • 在我的例子中,一个相关的问题是:derived d; printf("%d", static_cast(&d)==static_cast(&d.m));打印 1 还是 0?这决定了大小是 1 还是 >1。
    【解决方案3】:

    扩展我之前的评论:

    对象由其地址标识。如果比较相同类型的两个对象的地址(如指针)并且它们比较相等,则认为指针指向同一个对象。

    这种方式不能直接比较不同类型的对象,所以允许它们具有相同的地址。一个例子是一个结构和它的第一个成员。它们不能是同一类型。基类和派生类也不能,因此如果基类为空,它们可能具有相同的地址。

    但是,基类和派生类的第一个成员可以属于同一类型。这不是问题,除非基类也是空的并且编译器尝试空基类优化。在这种情况下,我们可以将指向相同类型的两个不同对象的指针比较相等,因此认为它们是同一个对象。

    因此,如果成员具有不同的类型(空和字符),它们可以具有相同的地址。如果它们属于同一类型,则不能,因为这会破坏对象身份的测试,例如 if (this != &that),有时用于测试自分配之类的东西。

    顺便说一句,微软同意这是他们编译器中的一个错误,但还有其他更紧急的事情需要先修复。

    【讨论】:

    • 顺便说一句,标准禁止 standard-layout 类具有与第一个成员相同类型的基类,大概正是出于这个原因。
    【解决方案4】:

    在这个线程中有很好的解释。我只是想补充一点来解决这个结构膨胀问题,您可以简单地将empty 类设为模板,以便使用不同的模板参数对其进行实例化使其成为不同的类:

    template<class T>
    struct empty { };
    struct member: empty<member> { };
    struct derived: empty<derived> { member m; };
    
    int main(void)
    {
        printf("%d\n", sizeof(derived));
        return 0;
    }
    

    输出1

    这就是避免在大型项目中使用boost::noncopyable 的原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-10
      • 1970-01-01
      • 2015-10-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多