Nik*_*sov 246
填充 将结构成员与"自然"地址边界对齐 - 比如,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;
Run Code Online (Sandbox Code Playgroud)
另一方面,打包阻止编译器进行填充 - 这必须明确请求 - 在GCC下__attribute__((__packed__)),所以如下:
struct __attribute__((__packed__)) mystruct_A {
char a;
int b;
char c;
};
Run Code Online (Sandbox Code Playgroud)
将6在32位架构上产生大小结构.
但需要注意的是 - 在允许它的架构上(如x86和amd64),未对齐的内存访问速度较慢,并且在严格的对齐架构(如SPARC )上明确禁止.
Eri*_*ang 47
(上面的答案解释了原因很清楚,但似乎并不完全清楚填充的大小,因此,我将根据我从C结构包装的遗失艺术中学到的东西添加答案)
规则:
C应该从可被4整除的地址开始,并且Go以8 Rust乘2开始.int并且long是特殊的,可以是任何内存地址,因此它们不需要在它们之前填充.short,除了每个单独成员的对齐需要之外,整个结构本身的大小将被对齐到可被最大单个成员的大小整除的大小,通过末端填充.char可以被8整除,char[]那么可以被4 整除,struct然后被2 整除.会员顺序:
long和int来自实施例下面具有相同的部件,但以不同的顺序,并导致不同的大小为2层结构.规则:
short字节开始.(您可以在下面的示例中看到,结构的所有打印十六进制地址都以此结尾stu_c.)stu_d).空的空间:
(n * 16)下面,变量0位于相邻结构long double和之间char.test_struct_address()声明,x地址都不会改变,g只是重用h浪费的空白空间.x.(对于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;
}
Run Code Online (Sandbox Code Playgroud)
执行结果 - h:
stu_a: 8
stu_b: 16
stu_c: 24
stu_d: 16
stu_e: 16
stu_f: 24
stu_g: 4
stu_h: 8
Run Code Online (Sandbox Code Playgroud)
执行结果 - x:
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
Run Code Online (Sandbox Code Playgroud)
因此,每个变量的地址开始是g:d0 x:dc h:e0 y:e8
Ian*_*anC 41
我知道这个问题很老,这里的大多数答案都很好地解释了填充,但是在我自己试图理解它时,我觉得有一个"视觉"形象正在发生的事情有所帮助.
处理器以确定大小(字)的"块"读取存储器.假设处理器字长8个字节.它会将内存视为8字节构建块的大行.每当它需要从内存中获取一些信息时,它将到达其中一个块并获得它.

如上图所示,Char(长度为1个字节)的位置无关紧要,因为它位于其中一个块内部,要求CPU只处理1个字.
当我们处理大于一个字节的数据时,如4字节的int或8字节的双字节,它们在内存中的对齐方式会对CPU必须处理的字数产生影响.如果4字节块以某种方式对齐,它们总是适合块的内部(存储器地址是4的倍数),则只需要处理一个字.否则,一个4字节的块可能在一个块上有一部分,而在另一个块上有一部分,要求处理器处理2个字以读取该数据.
这同样适用于8字节的双精度数,除非它现在必须位于8的存储器地址中,以保证它始终位于块内.
这考虑了一个8字节的字处理器,但这个概念适用于其他大小的字.
填充通过填充这些数据之间的间隙来确保它们与这些块对齐,从而在读取内存时提高性能.
然而,正如其他人所说的那样,有时候空间比表演本身更重要.也许你在没有太多RAM的计算机上处理大量数据(可以使用交换空间,但速度要慢得多).您可以在程序中安排变量,直到填充最少(因为它在其他一些答案中得到了很好的例证),但如果这还不够,您可以明确禁用填充,这就是打包.
小智 20
结构填料抑制结构填料,对准最重要时使用的填料,空间最重要时使用的填料.
一些编译器提供#pragma抑制填充或将其打包为n个字节.有些提供关键字来执行此操作.通常用于修改结构填充的pragma将采用以下格式(取决于编译器):
#pragma pack(n)
Run Code Online (Sandbox Code Playgroud)
例如,ARM提供了__packed用于抑制结构填充的关键字.阅读编译器手册以了解更多相关信息.
因此,打包结构是没有填充的结构.
通常使用包装结构
节省空间
格式化数据结构以使用某种协议通过网络传输(当然,这不是一个好的做法,因为你需要
处理字节序)
填充和包装只是同一件事的两个方面:
在mystruct_A,假设默认对齐为4,每个成员在4个字节的倍数上对齐.由于大小char为1时,对于填充a和c是4 - 1 = 3字节,而无需对填充int b这已经是4个字节.它的工作方式相同mystruct_B.
变量存储在可被其对齐方式(通常是其大小)整除的任何地址。因此,填充/打包不仅仅适用于结构。实际上,所有数据都有自己的对齐要求:
\nint main(void) {\n // We assume the `c` is stored as first byte of machine word\n // as a convenience! If the `c` was stored as a last byte of previous\n // word, there is no need to pad bytes before variable `i`\n // because `i` is automatically aligned in a new word.\n\n char c; // starts from any addresses divisible by 1(any addresses).\n char pad[3]; // not-used memory for `i` to start from its address.\n int32_t i; // starts from any addresses divisible by 4.\nRun Code Online (Sandbox Code Playgroud)\n这与struct类似,但也有一些区别。首先,我们可以说有两种填充\xe2\x80\x94 a) 为了正确地从每个成员的地址开始,在成员之间插入一些字节。b) 为了从其地址正确启动下一个结构实例,需要将一些字节附加到每个结构中:
\n// Example for rule 1 below.\nstruct st {\n char c; // starts from any addresses divisible by 4, not 1.\n char pad[3]; // not-used memory for `i` to start from its address.\n int32_t i; // starts from any addresses divisible by 4.\n};\n\n// Example for rule 2 below.\nstruct st {\n int32_t i; // starts from any addresses divisible by 4.\n char c; // starts from any addresses.\n char pad[3]; // not-used memory for next `st`(or anything that has same\n // alignment requirement) to start from its own address.\n};\nRun Code Online (Sandbox Code Playgroud)\n4结构体的第一个成员始终从可被结构体自身的对齐要求整除的任何地址开始,结构体自身的对齐要求由最大成员的对齐要求(此处为 的对齐int32_t)确定。这与普通变量不同。普通变量可以从任何可被其对齐整除的地址开始,但结构体的第一个成员则不然。如您所知,结构体的地址与其第一个成员的地址相同。struct st arr[2];。为了使arr[1](arr[1]\的第一个成员)从可被4整除的地址开始,我们应该在每个结构体的末尾附加3个字节。这是我从《失落的结构包装艺术》中学到的。
\n注意:您可以通过运算符调查数据类型的对齐要求是什么_Alignof。另外,您可以通过宏获取结构体中成员的偏移量offsetof。