首页 文章

为什么C 11的POD“标准布局”定义是这样的?

提问于
浏览
37

我正在调查C++11中新的,轻松的POD定义(第9.7节)

标准布局类是一个类:没有非标准布局类(或此类类型的数组)或引用的非静态数据成员,没有虚函数(10.3)且没有虚基类(10.1) ),对所有非静态数据成员具有相同的访问控制(第11条),没有非标准布局基类,在最派生类中没有非静态数据成员,并且最多只有一个非基类-static数据成员,或者没有具有非静态数据成员的基类,并且没有与第一个非静态数据成员相同类型的基类 .

我突出了让我感到惊讶的一些事情 .

如果我们容忍具有不同访问控制的数据成员会出什么问题?

如果第一个数据成员也是基类会出什么问题?即

struct Foo {};
struct Good : Foo {int x; Foo y;};
struct Bad  : Foo {Foo y; int x;};

我承认这是一个奇怪的结构,但为什么要禁止 Bad 而不是 Good

最后,如果不止一个组成类有数据成员,会出现什么问题?

6 回答

  • 4

    您可以将标准布局类对象地址强制转换为指向其第一个成员的指针,然后返回后面的段落之一,这通常也在C中完成:

    struct A { int x; };
    A a;
    
    // "px" is guaranteed to point to a.x
    int *px = (int*) &a;
    
    // guaranteed to point to a
    A *pa = (A*)px;
    

    为了实现这一点,第一个成员和完整对象必须具有相同的地址(编译器不能通过任何字节调整int指针,因为它可以't know whether it'是 A 的成员) .

    最后,如果不止一个组成类有数据成员,会出现什么问题?

    在一个类中,成员根据声明顺序分配在增加的地址中 . 但是,C并未规定跨类的数据成员的分配顺序 . 如果派生类和基类都有数据成员,则标准不会故意定义其地址的顺序,以便为布局内存提供完全的灵活性 . 但是为了使上面的演员工作,您需要知道分配顺序中的“第一”成员是什么!

    如果第一个数据成员也是基类,会出现什么问题?

    如果基类与第一个数据成员具有相同的类型,那么将基类放在内存中的派生类对象之前的实现需要在内存中的派生类对象数据成员之前有一个填充字节(基类的大小为1) ),以避免基类和第一个数据成员具有相同的地址(在C中,相同类型的两个不同对象始终具有不同的地址) . 但是,再次使得派生类对象的地址转换为其第一个数据成员的类型也是不可能的 .

  • 22

    它基本上与C 03和C的兼容性:

    • 相同的访问控制 - 允许C03实现使用访问控制说明符作为重新排序类(组)成员的机会,例如为了更好地打包它 .

    • 层次结构中有多个具有非静态数据成员的类 - C 03没有说明基类所在的位置,或者是否在相同类型的完整对象中存在的基类子对象中省略了填充 .

    • 基类和相同类型的第一个成员 - 由于第二个规则,如果基类类型用于数据成员,则它必须是空类 . 许多编译器都实现了空基类优化,因此Andreas对具有相同地址的子对象的说法是正确的 . 我对基类子对象的地址与同一类型的第一个数据成员具有相同的地址是不好的,但由于不同的相同类型的对象具有不同的地址,即使它们是空的子对象,它也不会't matter when the base class subobject has the same address as a first data member of a different type. [Edit: it' . 感谢Johannes]

    C 0x可能已经定义了那些东西也是标准布局类型,在这种情况下它也会定义它们如何进一步讨论这个问题,看看他的标准布局类的一个很好的属性的例子,这些东西会干扰用 .

    但如果它这样做,那么一些实现将被迫改变它们如何布置类以匹配新要求,这对于C 0x之前和之后的编译器的不同版本之间的结构兼容性是一种麻烦 . 它基本上打破了C ABI .

    我对如何定义标准布局的理解是,他们研究了在不破坏现有条件的情况下可以放松的POD要求实现 . 所以我假设没有检查,上面是一些示例,其中一些现有的C 03实现确实使用类的非POD特性来做一些与标准布局不兼容的事情 .

  • 21

    如果我们容忍具有不同访问控制的数据成员会出现什么问题?

    当前语言表示编译器无法在同一访问控制下重新排序成员 . 喜欢:

    struct x
    {
    public:
        int x;
        int y;
    private:
        int z;
    };
    

    这里x必须在y之前分配,但z相对于x和y没有限制 .

    struct y
    {
    public:
        int x;
    public:
        int y;
    };
    

    新的措辞说,尽管有两个 publicy 仍然是POD . 这实际上是对规则的放宽 .

  • 7

    至于为什么 Bad 不允许让我从我发现的一篇文章中退出:

    这确保了具有相同类类型且属于同一最派生对象的两个子对象不在同一地址分配 .

    http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2172.html

  • 1

    从子弹5开始,似乎两者都是非pod,因为大多数派生类都有非静态数据成员(int),它不能有一个带有非静态数据成员的基类 .

    我理解为:“只有一个”基类“(即类本身或它继承的类之一)可以拥有非静态数据成员”

  • 2

    struct Good 也不是标准布局,因为Foo和Good具有非静态数据成员 .

    这样,Good应该是:

    struct Foo {int foo;};
    struct Good : public Foo {Foo y;};
    

    它不能满足第6个子弹 . 因此第6弹?

相关问题