结构中字段对齐的奇怪行为
Strange behavior of fields aligment in the structure
#include <iostream>
using namespace std;
#pragma pack(push, 4)
struct Foo
{
char ch; //1
char ch2; //1
char ch3; //1
char ch4; //1 _4
char ch5; //1
short num; //2
char ch6; //1 _4
int num2; //4 _4
};
#pragma pack(pop)
int main() {
cout << sizeof( Foo );
return 0;
}
为什么输出是16字节?我认为它必须是 12,因为:
4 char = 4 bytes
char + short + char = 4 bytes
int = 4 bytes
那么谁能解释一下剩下的 4 个字节在哪里?
你得到的是内存中的以下内容(|s 是 4 字节边界):
|char char char char|char 1bytePadding short|char 3bytesPadding|int|
中的short
需要对齐2byte的边界,所以在前面的char
后面插入一个字节的padding即可。类似地,int
必须是 4 字节对齐的,所以在 char
之后必须插入 3 个字节的填充,这样就可以了。如果您正在针对 space 进行优化,则经验法则是从最大到最小对成员进行排序。如果您这样做,它将是:
|int|short char char| char char char char|
如您所料,这将占用 12 个字节。
这与对齐有关。
简而言之,如果不同的值在内存中 "aligned",CPU 会更喜欢。例如,如果你正在处理一个标准的 32 位整数(4 个字节),大多数 CPU 会希望它驻留在一个可以被 4 整除的内存地址中。因此,10004
的内存地址就可以了, 10008
可以,但 10005
不行。
大多数 CPU 在处理未对齐的值时会抛出异常并拒绝处理。然而,我们可信赖的 x86 是一个例外,并且会正确处理它——尽管速度要慢得多。在幕后,它将从内存中获取 2 个对齐的整数,然后旋转位以从中提取未对齐的整数。 (在其他平台上,我认为编译器会生成额外的指令来完成这项工作,但我不确定)所以你真的不希望这种情况发生,除非你有一个很好的理由。
这就是为什么您的编译器在 struct
成员之间生成一些填充字节的原因 - 这样 short
将位于偶数地址而 int
将位于地址可以被 4 整除。
#pragma pack
可以影响这一点,但前提是您将其设置为小于 4
。你会遇到我之前提到的对齐问题。
这是带有偏移量的结构:
struct Foo
{
0: char ch; //1
1: char ch2; //1
2: char ch3; //1
3: char ch4; //1
4: char ch5; //1
5: _padding //1
6: short num; //2
8: char ch6; //1
9: _padding //3
12: int num2; //4
16:
};
因为你的平台上的 short
有 2 个字节对齐,在 num
之前添加了 1 个字节填充以使其偏移量可以被 2 整除。然后 ch6
之后有 3 个字节到使 num2
的偏移量可以被 4 整除。
#include <iostream>
using namespace std;
#pragma pack(push, 4)
struct Foo
{
char ch; //1
char ch2; //1
char ch3; //1
char ch4; //1 _4
char ch5; //1
short num; //2
char ch6; //1 _4
int num2; //4 _4
};
#pragma pack(pop)
int main() {
cout << sizeof( Foo );
return 0;
}
为什么输出是16字节?我认为它必须是 12,因为:
4 char = 4 bytes
char + short + char = 4 bytes
int = 4 bytes
那么谁能解释一下剩下的 4 个字节在哪里?
你得到的是内存中的以下内容(|s 是 4 字节边界):
|char char char char|char 1bytePadding short|char 3bytesPadding|int|
中的short
需要对齐2byte的边界,所以在前面的char
后面插入一个字节的padding即可。类似地,int
必须是 4 字节对齐的,所以在 char
之后必须插入 3 个字节的填充,这样就可以了。如果您正在针对 space 进行优化,则经验法则是从最大到最小对成员进行排序。如果您这样做,它将是:
|int|short char char| char char char char|
如您所料,这将占用 12 个字节。
这与对齐有关。
简而言之,如果不同的值在内存中 "aligned",CPU 会更喜欢。例如,如果你正在处理一个标准的 32 位整数(4 个字节),大多数 CPU 会希望它驻留在一个可以被 4 整除的内存地址中。因此,10004
的内存地址就可以了, 10008
可以,但 10005
不行。
大多数 CPU 在处理未对齐的值时会抛出异常并拒绝处理。然而,我们可信赖的 x86 是一个例外,并且会正确处理它——尽管速度要慢得多。在幕后,它将从内存中获取 2 个对齐的整数,然后旋转位以从中提取未对齐的整数。 (在其他平台上,我认为编译器会生成额外的指令来完成这项工作,但我不确定)所以你真的不希望这种情况发生,除非你有一个很好的理由。
这就是为什么您的编译器在 struct
成员之间生成一些填充字节的原因 - 这样 short
将位于偶数地址而 int
将位于地址可以被 4 整除。
#pragma pack
可以影响这一点,但前提是您将其设置为小于 4
。你会遇到我之前提到的对齐问题。
这是带有偏移量的结构:
struct Foo
{
0: char ch; //1
1: char ch2; //1
2: char ch3; //1
3: char ch4; //1
4: char ch5; //1
5: _padding //1
6: short num; //2
8: char ch6; //1
9: _padding //3
12: int num2; //4
16:
};
因为你的平台上的 short
有 2 个字节对齐,在 num
之前添加了 1 个字节填充以使其偏移量可以被 2 整除。然后 ch6
之后有 3 个字节到使 num2
的偏移量可以被 4 整除。