考虑:
struct mystruct_A
{
char a;
int b;
char c;
} x;
struct mystruct_B
{
int b;
char a;
} y;
结构的尺寸分别为12和8。
这些结构是填充还是打包?
何时进行填充或包装?
答案 0 :(得分:232)
将 aligns结构成员填充到“自然”地址边界 - 例如,int
成员将具有偏移量,在32位平台上为mod(4) == 0
。填充默认情况下处于启用状态它将以下“间隙”插入到您的第一个结构中:
struct mystruct_A {
char a;
char gap_0[3]; /* inserted by compiler: for alignment of b */
int b;
char c;
char gap_1[3]; /* -"-: for alignment of the whole struct in an array */
} x;
另一方面, 打包会阻止编译器执行填充 - 必须明确请求 - 在GCC下它是__attribute__((__packed__))
,所以如下:
struct __attribute__((__packed__)) mystruct_A {
char a;
int b;
char c;
};
将在32位架构上生成大小为6
的结构。
但是注意 - 在允许它的架构上(如x86和amd64),未对齐的内存访问速度较慢,并且在严格对齐架构(如SPARC)上明确禁止。
答案 1 :(得分:35)
我知道这个问题已经过时了,这里的大多数答案都很好地解释了填充,但是在尝试自己理解填充的同时,我认为对所发生的事情有一个“视觉”形象的帮助。
处理器以确定大小(字)的“块”读取内存。假设处理器字长8个字节。它会将内存视为8字节构建块的大行。每次需要从内存中获取一些信息时,它都会到达其中一个块并获得它。
如上图所示,Char(长度为1个字节)的位置无关紧要,因为它位于其中一个块内,需要CPU只处理1个字。
当我们处理大于一个字节的数据时,比如4字节的int或8字节的双字节,它们在内存中的对齐方式会对CPU必须处理的字数产生影响。如果4字节块以某种方式对齐,它们总是适合块的内部(存储器地址是4的倍数),则只需要处理一个字。否则,一个4字节的块可能在一个块上有一部分,另一块在另一个块上,需要处理器处理2个字来读取这些数据。
同样适用于8字节的双精度数,除非它现在必须在8的存储器地址中,以保证它总是在一个块内。
这考虑了一个8字节的字处理器,但这个概念适用于其他大小的字。
填充通过填充这些数据之间的间隙来确保它们与这些块对齐,从而在读取内存时提高性能。
然而,正如其他人的答案所述,有时候空间比表演本身更重要。也许你在没有太多RAM的计算机上处理大量数据(可以使用交换空间,但速度要慢得多)。您可以在程序中排列变量,直到最少填充为止(因为它在其他一些答案中得到了很好的例证),但如果这还不够,则可以明确禁用填充,这就是打包。< / p>
答案 2 :(得分:33)
(上面的答案解释了原因很清楚,但似乎并不完全清楚填充的大小,因此,我将根据我从 C结构包装的遗失艺术中学到的内容添加一个答案强> 的)
<强>规则:强>
int
应从可被4整除的地址开始,long
加8,short
加2。char
和char[]
是特殊的,可以是任何内存地址,因此它们不需要填充。struct
,除了每个单独成员的对齐需要之外,整个结构本身的大小将对齐到可被最大单个成员的大小整除的大小,通过末尾填充。long
,那么可以被8整除,int
然后被4,short
再减2。会员顺序:
stu_c
和stu_d
具有相同的成员,但顺序不同,导致2个结构的大小不同。<强>规则:强>
(n * 16)
个字节开始。 (您可以在下面的示例中看到,结构的所有打印十六进制地址都以0
结尾。)long double
)。空白区域:
test_struct_address()
下面,变量x
位于相邻结构g
和h
之间。x
,h
的地址都不会更改,x
只是重复使用g
浪费的空白区域。y
。(64位系统的 )
<强> memory_align.c 强>:
/**
* Memory align & padding - for struct.
* compile: gcc memory_align.c
* execute: ./a.out
*/
#include <stdio.h>
// size is 8, 4 + 1, then round to multiple of 4 (int's size),
struct stu_a {
int i;
char c;
};
// size is 16, 8 + 1, then round to multiple of 8 (long's size),
struct stu_b {
long l;
char c;
};
// size is 24, l need padding by 4 before it, then round to multiple of 8 (long's size),
struct stu_c {
int i;
long l;
char c;
};
// size is 16, 8 + 4 + 1, then round to multiple of 8 (long's size),
struct stu_d {
long l;
int i;
char c;
};
// size is 16, 8 + 4 + 1, then round to multiple of 8 (double's size),
struct stu_e {
double d;
int i;
char c;
};
// size is 24, d need align to 8, then round to multiple of 8 (double's size),
struct stu_f {
int i;
double d;
char c;
};
// size is 4,
struct stu_g {
int i;
};
// size is 8,
struct stu_h {
long l;
};
// test - padding within a single struct,
int test_struct_padding() {
printf("%s: %ld\n", "stu_a", sizeof(struct stu_a));
printf("%s: %ld\n", "stu_b", sizeof(struct stu_b));
printf("%s: %ld\n", "stu_c", sizeof(struct stu_c));
printf("%s: %ld\n", "stu_d", sizeof(struct stu_d));
printf("%s: %ld\n", "stu_e", sizeof(struct stu_e));
printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));
printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));
return 0;
}
// test - address of struct,
int test_struct_address() {
printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));
printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));
struct stu_g g;
struct stu_h h;
struct stu_f f1;
struct stu_f f2;
int x = 1;
long y = 1;
printf("address of %s: %p\n", "g", &g);
printf("address of %s: %p\n", "h", &h);
printf("address of %s: %p\n", "f1", &f1);
printf("address of %s: %p\n", "f2", &f2);
printf("address of %s: %p\n", "x", &x);
printf("address of %s: %p\n", "y", &y);
// g is only 4 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
printf("space between %s and %s: %ld\n", "g", "h", (long)(&h) - (long)(&g));
// h is only 8 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
printf("space between %s and %s: %ld\n", "h", "f1", (long)(&f1) - (long)(&h));
// f1 is only 24 bytes itself, but distance to next struct is 32 bytes(on 64 bit system) or 24 bytes(on 32 bit system),
printf("space between %s and %s: %ld\n", "f1", "f2", (long)(&f2) - (long)(&f1));
// x is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between g & h,
printf("space between %s and %s: %ld\n", "x", "f2", (long)(&x) - (long)(&f2));
printf("space between %s and %s: %ld\n", "g", "x", (long)(&x) - (long)(&g));
// y is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between h & f1,
printf("space between %s and %s: %ld\n", "x", "y", (long)(&y) - (long)(&x));
printf("space between %s and %s: %ld\n", "h", "y", (long)(&y) - (long)(&h));
return 0;
}
int main(int argc, char * argv[]) {
test_struct_padding();
// test_struct_address();
return 0;
}
执行结果 - test_struct_padding()
:
stu_a: 8
stu_b: 16
stu_c: 24
stu_d: 16
stu_e: 16
stu_f: 24
stu_g: 4
stu_h: 8
执行结果 - test_struct_address()
:
stu_g: 4
stu_h: 8
stu_f: 24
address of g: 0x7fffd63a95d0 // struct variable - address dividable by 16,
address of h: 0x7fffd63a95e0 // struct variable - address dividable by 16,
address of f1: 0x7fffd63a95f0 // struct variable - address dividable by 16,
address of f2: 0x7fffd63a9610 // struct variable - address dividable by 16,
address of x: 0x7fffd63a95dc // non-struct variable - resides within the empty space between struct variable g & h.
address of y: 0x7fffd63a95e8 // non-struct variable - resides within the empty space between struct variable h & f1.
space between g and h: 16
space between h and f1: 16
space between f1 and f2: 32
space between x and f2: -52
space between g and x: 12
space between x and y: 12
space between h and y: 8
因此每个变量的地址开始是g:d0 x:dc h:e0 y:e8
答案 3 :(得分:20)
结构填料抑制结构填充,对齐最重要时使用的填充,空间最重要时使用的填充。
某些编译器提供#pragma
来抑制填充或将其打包为n个字节。有些提供关键字来执行此操作。通常用于修改结构填充的pragma将采用以下格式(取决于编译器):
#pragma pack(n)
例如,ARM提供__packed
关键字来抑制结构填充。阅读编译器手册以了解更多相关信息。
所以打包结构是一个没有填充的结构。
将使用通常打包的结构
节省空间
格式化数据结构以通过网络传输一些 协议(当然这不是一个好习惯,因为你需要 处理字节顺序)
答案 4 :(得分:5)
填充和包装只是同一件事的两个方面:
在mystruct_A
中,假设默认对齐为4,每个成员对齐4个字节的倍数。由于char
的大小为1,因此a
和c
的填充为4 - 1 = 3个字节,而int b
的填充不需要填充,这已经是4个字节。它对mystruct_B
的工作方式相同。
答案 5 :(得分:2)
填充规则:
为什么规则2: 考虑以下结构,
如果我们要为此结构创建一个数组(包含2个结构), 最后无需填充:
因此,结构的大小= 8个字节
假设我们要创建另一个结构,如下所示:
如果我们要创建此结构的数组, 最后有两种填充字节的可能性。
A。如果我们在末尾添加3个字节并将其对齐为int而不是Long:
B。如果我们在末尾添加7个字节并将其与Long对齐:
第二个数组的起始地址是8的倍数(即24)。 结构的大小= 24字节
因此,通过将结构的下一个数组的起始地址与最大成员的倍数对齐(即,如果我们要创建此结构的数组,则第二个数组的第一个地址必须以一个是该结构的最大成员的倍数,这里是24(3 * 8)),我们可以计算最后所需的填充字节数。
答案 6 :(得分:1)
仅在您明确告知编译器打包结构时才进行结构打包。填充就是你所看到的。您的32位系统将每个字段填充到字对齐。如果您已经告诉编译器打包结构,它们分别是6和5个字节。不要这样做。它不可移植,并且使编译器生成更慢(有时甚至是错误)的代码。
答案 7 :(得分:0)
没有其他事情了! 想要掌握该主题的人必须做以下事情,
- 仔细阅读The Lost Art of Structure Packing,由Eric S. Raymond撰写
- 浏览Eric's code example
- 最后但并非最不重要的一点,请不要忘记以下有关填充的规则,即结构与最大类型的对齐方式对齐 要求。
答案 8 :(得分:0)
这些结构是填充的还是压缩的?
它们被填充了。
最初想到的唯一可能是,如果 char
和 int
的大小相同,那么它们的最小大小是char/int/char
结构不允许填充,int/char
结构也是如此。
但是,这需要 sizeof(int)
和 sizeof(char)
为 4(以获得 12 和 8 个尺寸)。整个理论都崩溃了,因为它由标准保证sizeof(char)
总是 1。
如果 char
和 int
的宽度相同,则尺寸应该是一加一,不是四加四。因此,为了获得 12 的大小,必须在最后一个字段之后进行填充。
什么时候进行填充或包装?
只要编译器实现需要它。编译器可以自由地在字段之间插入填充,并在最后一个字段之后(但不是在第一个字段之前)。
这通常是为了提高性能,因为某些类型在特定边界上对齐时性能更好。如果您尝试访问未对齐的数据(是的,我正在查看 你, ARM),甚至有些架构将拒绝运行(即崩溃)。
您通常可以使用特定于实现的功能(例如 #pragma pack
)来控制打包/填充(这实际上是同一范围的两端)。即使您不能在您的特定实现中这样做,您也可以在编译时检查您的代码以确保它满足您的要求(使用标准 C 功能,而不是特定于实现的东西)。
例如:
// C11 or better ...
#include <assert.h>
struct strA { char a; int b; char c; } x;
struct strB { int b; char a; } y;
static_assert(sizeof(struct strA) == sizeof(char)*2 + sizeof(int), "No padding allowed");
static_assert(sizeof(struct strB) == sizeof(char) + sizeof(int), "No padding allowed");
如果这些结构中有任何填充,这样的东西将拒绝编译。
答案 9 :(得分:0)
变量存储在任何可被其对齐(通常按其大小)整除的地址。因此,填充/打包不仅适用于结构。实际上,所有数据都有自己的对齐要求:
int main(void) {
// We assume the `c` is stored as first byte of machine word
// as a convenience! If the `c` was stored as a last byte of previous
// word, there is no need to pad bytes before variable `i`
// because `i` is automatically aligned in a new word.
char c; // starts from any addresses divisible by 1(any addresses).
char pad[3]; // not-used memory for `i` to start from its address.
int32_t i; // starts from any addresses divisible by 4.
这与struct类似,但有一些区别。首先,我们可以说有两种填充—— a) 为了正确地从每个成员的地址开始,在成员之间插入一些字节。 b) 为了正确地从它的地址开始下一个结构体实例,一些字节被附加到每个结构体:
// Example for rule 1 below.
struct st {
char c; // starts from any addresses divisible by 4, not 1.
char pad[3]; // not-used memory for `i` to start from its address.
int32_t i; // starts from any addresses divisible by 4.
};
// Example for rule 2 below.
struct st {
int32_t i; // starts from any addresses divisible by 4.
char c; // starts from any addresses.
char pad[3]; // not-used memory for next `st`(or anything that has same
// alignment requirement) to start from its own address.
};
4
,int32_t
的对齐)。这与普通变量不同。普通变量可以开始任何可被其对齐整除的地址,但结构的第一个成员不是这种情况。如您所知,结构体的地址与其第一个成员的地址相同。struct st arr[2];
。为了使 arr[1]
(arr[1]
的第一个成员)从地址开始被 4 整除,我们应该在每个结构体的末尾附加 3 个字节。这是我从The Lost Art of Structure Packing那里学到的。
注意:您可以通过 _Alignof
运算符调查数据类型的对齐要求是什么。此外,您还可以通过 offsetof
宏获取结构体内部成员的偏移量。