这是一个相当人为的系列类型。 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。
答案 0 :(得分:8)
这种行为是由于编译器被允许apply optimizations in a non-POD type(例如:C<A2>
)
它不适用于POD类型(例如:C<A>
)。
我已经通过Kerrek SB找到了一个非常有用的答案来解决这个相关问题: When extending a padded struct, why can't extra fields be placed in the tail padding?
另一方面,无论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("%lu\n", sizeof(B<C>));
printf("%lu\n", 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的特定情况下,有一个discussion关于此:
看起来ABI文档意味着需要重用尾部填充 在非POD中,但实际上并没有这么说。
考虑这种情况,作为规范的例子:
struct S1 {
virtual void f();
int i;
char c1;
};
struct S2 : public S1 {
char c2;
};
我认为ABI意味着说你放了&#34; c2&#34;在尾巴填充 S1。 (这就是G ++实现的,FWIW。)
看看Itanium ABI C++(这是the one GCC uses)指定尾部填充的内容:
此ABI仅使用POD的定义来决定是否在基类子对象的尾部填充中分配对象。虽然标准随着时间的推移扩大了POD的定义,但它们也禁止程序员直接读取或写入基类子对象的底层字节,例如memcpy。因此,即使在最保守的解释中,实现也可以在任何类的尾部填充中自由地分配对象,这些对象在C ++ 98中不是POD。这个ABI符合这一点。
此外,here是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;
}
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