包装非POD类型:无警告,尺寸意外

Packing non-POD type: no warning, unexpected size?

本文关键字:意外 警告 POD 类型 包装      更新时间:2023-10-16

这是一个相当人为的类型系列。 A2只是A的非 POD 版本:

template <size_t N>
struct A { 
    char data[N];
} __attribute__((packed));
template <size_t N>
struct A2 {
    A2() { // actual body not significant
        memset(data, 0, N); 
    }   
    char data[N];
} __attribute__((packed));
template <template <size_t> class T>
struct C { 
    T<9> a;
    int32_t i;
    T<11> t2; 
} __attribute__((packed));
//}; // oops, forgot to pack
template <template <size_t> class T>
struct B : C<T> {
    char footer;
} __attribute__((packed));

按原样,sizeof(B<A>) == sizeof(B<A2>) == 25。我没有得到使用 -Wall -pedantic 编译的警告(这是 gcc 4.9.0)。

但是现在,假设我忘了打包C.现在我得到:

sizeof(B<A>) == 32
sizeof(B<A2>) == 28

仍然没有警告。这里发生了什么?B<A2>怎么比B<A>小?这是否只是由于A2不是 POD 而导致的未定义行为?

如果我重新组织B以查看具有C<T>成员而不是从它继承,那么只有这样我才会收到警告:

忽略打包属性,因为解压缩的非 POD 字段"C B::c"

[更新] 作为对IdeaHat评论的回应,以下是其他一些抵消:

        B<A>  B<A2>
a       0     0
i       12    12
t2      16    16
footer  28    27

sizeof(C<A>) == 28
sizeof(C<A2>) == 28

[更新 2] 我在 clang 上看到关于偏移量的行为相同,只是sizeof(B<A>)是 29 而不是 32。

该行为是由于编译器被允许在非 POD 类型中应用优化(例如:C<A2> )。
它不适用于 POD 类型(例如:C<A> )。

我发现了这个相关的问题,Kerrek SB给出了一个非常有用的答案:
扩展填充结构时,为什么不能在尾部填充中放置额外的字段?

另一方面,无论 POD 在 GCC 中使用 -fpack-struct 选项如何,您都可以强制进行此优化。虽然不推荐,但它对示例很有用。

#include <stdint.h>
#include <stdio.h>
struct C {
    int16_t i;
    char t[1];
};
struct C2 {
    C2() {}
    int16_t i;
    char t[1];
};
template <class T>
struct B : T {
    char footer;
};
int main(void) {
    printf("%lun", sizeof(B<C>));
    printf("%lun", sizeof(B<C2>));
    return 0;
}

如果使用-fpack-struct (gcc-4.7) 编译它:

sizeof(B<C>) == sizeof(B<C2>) == 4

如果没有:

sizeof(B<C>) == 6
sizeof(B<C2>) == 4

来自 man gcc (4.7):

-fpack-struct[=n]
       Without a value specified, pack all structure members together
       without holes. 
       When a value is specified (which must be a small power of two),
       pack structure members according to this value, representing
       the maximum alignment (that is, objects with default alignment
       requirements larger than this will be output potentially
       unaligned at the next fitting location.
       Warning: the -fpack-struct switch causes GCC to generate code
       that is not binary compatible with code generated without that
       switch. 
       Additionally, it makes the code suboptimal. Use it to conform
       to a non-default application binary interface.

如您所见,当一个类是 POD 并且它充当另一个类的基类时,除非您强制它,否则该基不会打包。 即:它不使用底座的尾部填充


在GCC使用的C++ ABI的特殊情况下,有一个关于这个的讨论:

看起来 ABI 文档意味着要求重用尾部填充 在非 POD 中,但它实际上并没有这么说。

考虑这种情况,作为规范示例:

struct S1 {
    virtual void f();
    int i;
    char c1;
};
struct S2 : public S1 {
    char c2;
};

我认为 ABI 的意思是说您将"c2"放在尾部填充中 S1. (这就是 G++ 实现的,FWIW。


看看Itanium ABI C++(这是GCC使用的那个)对尾部填充的规定:

此 ABI 仅使用 POD 的定义来决定是否在基类子对象的尾边距中分配对象。虽然随着时间的推移,这些标准扩大了 POD 的定义,但它们也禁止程序员直接读取或写入基类子对象的底层字节,比如 memcpy。因此,即使在最保守的解释中,实现也可以在任何类的尾部填充中自由分配对象,这些对象在 C++98 中不会是 POD。该ABI符合这一点。


此外,以下是 C++ ABI 不使用 POD 对象的尾部填充的原因:

我们忽略了 POD 的尾部填充,因为该标准的早期版本不允许我们将它用于其他任何事情,并且因为它有时允许更快地复制类型。

在您的示例中,C<A> 是 POD,因此,当类型充当 B<A> 的基类时,ABI 不使用其尾部填充。
为此,C<A>保留填充(并占用 28 个字节),footer占用 4 个字节以遵守对齐方式。


最后,我想分享我用来做一些测试的代码,之前是为了找到一个正确的答案。您可以发现它很有用,以便查看编译器 ABI 如何处理 C++(11) (GCC) 中的对象。

测试代码

#include <iostream>
#include <stddef.h>
struct C {
    int16_t i;
    char t[1];
};
struct C2 {
    C2() {}
    int16_t i;
    char t[1];
};
template <class T>
struct B : T {
    char footer;
};
int main(void) {
    std::cout << std::boolalpha;
    std::cout << "standard_layout:" << std::endl;
    std::cout << "C: " << std::is_standard_layout<C>::value << std::endl;
    std::cout << "C2: " << std::is_standard_layout<C2>::value << std::endl;
    std::cout << "B<C>: " << std::is_standard_layout<B<C>>::value << std::endl;
    std::cout << "B<C2>: " << std::is_standard_layout<B<C2>>::value << std::endl;
    std::cout << std::endl;
    std::cout << "is_trivial:" << std::endl;
    std::cout << "C: " << std::is_trivial<C>::value << std::endl;
    std::cout << "C2: " << std::is_trivial<C2>::value << std::endl;
    std::cout << "B<C>: " << std::is_trivial<B<C>>::value << std::endl;
    std::cout << "B<C2>: " << std::is_trivial<B<C2>>::value << std::endl;
    std::cout << std::endl;
    std::cout << "is_pod:" << std::endl;
    std::cout << "C: " << std::is_pod<C>::value << std::endl;
    std::cout << "C2: " << std::is_pod<C2>::value << std::endl;
    std::cout << "B<C>: " << std::is_pod<B<C>>::value << std::endl;
    std::cout << "B<C2>: " << std::is_pod<B<C2>>::value << std::endl;
    std::cout << std::endl;
    std::cout << "offset:" << std::endl;
    std::cout << "C::i offset " << offsetof(C, i) << std::endl;
    std::cout << "C::t offset " << offsetof(C, t) << std::endl;
    std::cout << "C2::i offset " << offsetof(C2, i) << std::endl;
    std::cout << "C2::t offset " << offsetof(C2, t) << std::endl;
    B<C> bc;
    std::cout << "B<C>.i: " << (int)(reinterpret_cast<char*>(&bc.i) - reinterpret_cast<char*>(&bc)) << std::endl;
    std::cout << "B<C>.t: " << (int)(reinterpret_cast<char*>(&bc.t) - reinterpret_cast<char*>(&bc)) << std::endl;
    std::cout << "B<C>.footer: " << (int)(reinterpret_cast<char*>(&bc.footer) - reinterpret_cast<char*>(&bc)) << std::endl;
    B<C2> bc2;
    std::cout << "B<C2>.i: " << (int)(reinterpret_cast<char*>(&bc2.i) - reinterpret_cast<char*>(&bc2)) << std::endl;
    std::cout << "B<C2>.t: " << (int)(reinterpret_cast<char*>(&bc2.t) - reinterpret_cast<char*>(&bc2)) << std::endl;
    std::cout << "B<C2>.footer: " << (int)(reinterpret_cast<char*>(&bc2.footer) - reinterpret_cast<char*>(&bc2)) << std::endl;
    std::cout << std::endl;
    std::cout << "sizeof:" << std::endl;
    std::cout << "C: " << sizeof(C) << std::endl;
    std::cout << "C2: " << sizeof(C2) << std::endl;
    std::cout << "DIFFERENCE:n";
    std::cout << "B<C>: " << sizeof(B<C>) << std::endl;
    std::cout << "B<C2>: " << sizeof(B<C2>) << std::endl;
    std::cout << "B<C>::C: " << sizeof(B<C>::C) << std::endl;
    std::cout << "B<C2>::C: " << sizeof(B<C2>::C2) << std::endl;
    std::cout << std::endl;
    std::cout << "alignment:" << std::endl;
    std::cout << "C: " << std::alignment_of<C>::value << std::endl;
    std::cout << "C2: " << std::alignment_of<C2>::value << std::endl;
    std::cout << "B<C>: " << std::alignment_of<B<C>>::value << std::endl;
    std::cout << "B<C2>: " << std::alignment_of<B<C2>>::value << std::endl;
    std::cout << "B<C>::C: " << std::alignment_of<B<C>::C>::value << std::endl;
    std::cout << "B<C2>::C2: " << std::alignment_of<B<C2>::C2>::value << std::endl;
    std::cout << "B<C>.i: " << std::alignment_of<decltype(std::declval<B<C>>().i)>::value << std::endl;
    std::cout << "B<C>.t: " << std::alignment_of<decltype(std::declval<B<C>>().t)>::value << std::endl;
    std::cout << "B<C>.footer: " << std::alignment_of<decltype(std::declval<B<C>>().footer)>::value << std::endl;
    std::cout << "B<C2>.i: " << std::alignment_of<decltype(std::declval<B<C2>>().i)>::value << std::endl;
    std::cout << "B<C2>.t: " << std::alignment_of<decltype(std::declval<B<C2>>().t)>::value << std::endl;
    std::cout << "B<C2>.footer: " << std::alignment_of<decltype(std::declval<B<C2>>().footer)>::value << std::endl;
    return 0;
}

使用 gcc-4.7 输出

standard_layout:
C: true
C2: true
B<C>: false
B<C2>: false
is_trivial:
C: true
C2: false
B<C>: true
B<C2>: false
is_pod:
C: true
C2: false
B<C>: false
B<C2>: false
offset:
C::i offset 0
C::t offset 2
C2::i offset 0
C2::t offset 2
B<C>.i: 0
B<C>.t: 2
B<C>.footer: 4
B<C2>.i: 0
B<C2>.t: 2
B<C2>.footer: 3
sizeof:
C: 4
C2: 4
DIFFERENCE:
B<C>: 6
B<C2>: 4
B<C>::C: 4
B<C2>::C: 4
alignment:
C: 2
C2: 2
B<C>: 2
B<C2>: 2
B<C>::C: 2
B<C2>::C2: 2
B<C>.i: 2
B<C>.t: 1
B<C>.footer: 1
B<C2>.i: 2
B<C2>.t: 1
B<C2>.footer: 1