考虑:
struct mystruct_A
{
char a;
int b;
char c;
} x;
struct mystruct_B
{
int b;
char a;
} y;
结构的大小分别为12和8。
这些结构是被填充还是包装好?
填充或包装何时进行?
padding
使事情变得更大。packing
使事情变得更小。完全不同。
考虑:
struct mystruct_A
{
char a;
int b;
char c;
} x;
struct mystruct_B
{
int b;
char a;
} y;
结构的大小分别为12和8。
这些结构是被填充还是包装好?
填充或包装何时进行?
padding
使事情变得更大。packing
使事情变得更小。完全不同。
Answers:
填充 将结构成员与“自然”地址边界对齐 -例如,int
成员将具有偏移量,该偏移量mod(4) == 0
在32位平台上。默认情况下启用填充。它将以下“空白”插入第一个结构:
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;
另一方面,Packing阻止编译器进行填充(必须明确请求填充),在GCC下,它是__attribute__((__packed__))
,因此:
struct __attribute__((__packed__)) mystruct_A {
char a;
int b;
char c;
};
将6
在32位架构上生成大小结构。
不过请注意-未对齐的内存访问在允许它的体系结构(如x86和amd64)上速度较慢,并且在严格的对齐体系结构(如SPARC)上被明确禁止。
(上面的答案很清楚地解释了原因,但是关于填充的大小似乎并不十分清楚,因此,我将根据我从“结构包装的失落的艺术”中学到的知识来添加一个答案,它已经演变到不仅限于C
。也适用于Go
,Rust
。)
规则:
int
应该通过在4整除地址开始,并long
通过如图8所示,short
由2。char
并且char[]
很特殊,可以是任何内存地址,因此它们之前不需要填充。struct
,除了每个单独成员的对齐需要之外,整个结构本身的大小将通过末端填充而对齐为可被最大单个成员的大小整除的大小。long
然后将struct的最大成员除以8,int
然后除以4,short
然后乘以2。会员顺序:
stu_c
and stu_d
from实例具有相同的成员,但是顺序不同,并且导致2个结构的大小不同。规则:
(n * 16)
字节开始。(您可以在下面的示例中看到,所有打印的结构十六进制地址都以0
。结尾。)long double
)。char
as成员,则其地址可以从任何地址开始。可用空间:
test_struct_address()
下面,该变量x
位于相邻的struct 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
<The Lost Art of C Structure Packing>
很好地解释了规则,甚至认为它比这个答案要长一点。该书可在线免费获得:catb.org/esr/structure-packing
我知道这个问题很老,这里的大多数答案都很好地解释了填充,但是我自己试图理解它时,发现对正在发生的事情具有“视觉”印象是有帮助的。
处理器以一定大小(字)的“块”形式读取内存。假设处理器字长为8个字节。它将内存看成是8字节构建块的大行。每当它需要从内存中获取一些信息时,它将到达这些块之一并获取它。
如上图所示,Char(1个字节长)在哪里都无所谓,因为它将位于这些块之一中,因此CPU仅需要处理1个字。
当我们处理大于一个字节的数据时,例如4字节的int或8字节的double,它们在内存中的对齐方式将影响CPU必须处理多少个字。如果将4字节的块对齐,使其始终适合块的内部(内存地址为4的倍数),则仅需处理一个字。否则,一块4字节的块可能一部分在一个块上,另一部分在另一个块上,这要求处理器处理2个字来读取此数据。
同样适用于8字节的double,但是现在它必须位于8的内存地址中,以确保它始终位于一个块内。
这考虑了8字节的字处理器,但是该概念适用于其他大小的字。
填充通过填充这些数据之间的间隙以确保它们与这些块对齐来起作用,从而在读取存储器时提高了性能。
但是,正如其他答案所述,有时空间比性能本身更重要。也许您正在没有太多RAM的计算机上处理大量数据(可以使用交换空间,但速度要慢得多)。您可以在程序中排列变量,直到完成最少的填充为止(这在其他一些答案中得到了很好的说明),但是,如果这还不够的话,您可以显式禁用填充(即打包)。
有没有关于它!想要掌握主题的人必须做以下事情,
- 阅读埃里克·雷蒙德(Eric S.Raymond)撰写的《结构包装的迷失的艺术》
- 一览Eric的代码示例
- 最后但并非最不重要的一点是,不要忘记以下有关填充的规则,即 结构要与最大类型的对齐要求对齐。
填充规则:
为什么要使用规则2:请考虑以下结构,
如果我们要创建此结构的数组(包含2个结构),则最后不需要填充:
因此,struct的大小= 8个字节
假设我们要创建另一个结构,如下所示:
如果要创建此结构的数组,则有两种可能性,即最后所需的填充字节数。
答:如果我们在末尾添加3个字节并将其对齐为int而不是Long:
B.如果我们在末尾添加7个字节并将其对齐为Long:
第二个数组的起始地址是8的倍数(即24)。结构的大小= 24字节
因此,通过将结构的下一个数组的起始地址与最大成员的倍数对齐(即,如果我们要创建此结构的数组,则第二个数组的第一个地址必须以整数倍的地址开始结构的最大成员的最大字节数,这里是24(3 * 8)),我们可以计算最后所需的填充字节数。
数据结构对齐是在计算机内存中排列和访问数据的方式。它包含两个独立但相关的问题:数据对齐和数据结构填充。当现代计算机读取或写入内存地址时,它将以字大小的块(例如32位系统上的4字节块)或更大的块大小执行此操作。数据对齐意味着将数据放在等于字长的倍数的内存地址上,这由于CPU处理内存的方式而提高了系统的性能。为了对齐数据,可能有必要在最后一个数据结构的末尾与下一个数据结构的末尾(即数据结构填充)之间插入一些无意义的字节。