结构填料和包装

时间:2010-11-29 17:16:13

标签: c struct structure padding packing

考虑:

struct mystruct_A
{
   char a;
   int b;
   char c;
} x;

struct mystruct_B
{
   int b;
   char a;
} y;

结构的尺寸分别为12和8。

这些结构是填充还是打包?

何时进行填充或包装?

10 个答案:

答案 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字节构建块的大行。每次需要从内存中获取一些信息时,它都会到达其中一个块并获得它。

Variables Alignment

如上图所示,Char(长度为1个字节)的位置无关紧要,因为它位于其中一个块内,需要CPU只处理1个字。

当我们处理大于一个字节的数据时,比如4字节的int或8字节的双字节,它们在内存中的对齐方式会对CPU必须处理的字数产生影响。如果4字节块以某种方式对齐,它们总是适合块的内部(存储器地址是4的倍数),则只需要处理一个字。否则,一个4字节的块可能在一个块上有一部分,另一块在另一个块上,需要处理器处理2个字来读取这些数据。

同样适用于8字节的双精度数,除非它现在必须在8的存储器地址中,以保证它总是在一个块内。

这考虑了一个8字节的字处理器,但这个概念适用于其他大小的字。

填充通过填充这些数据之间的间隙来确保它们与这些块对齐,从而在读取内存时提高性能。

然而,正如其他人的答案所述,有时候空间比表演本身更重要。也许你在没有太多RAM的计算机上处​​理大量数据(可以使用交换空间,但速度要慢得多)。您可以在程序中排列变量,直到最少填充为止(因为它在其他一些答案中得到了很好的例证),但如果这还不够,则可以明确禁用填充,这就是打包。< / p>

答案 2 :(得分:33)

上面的答案解释了原因很清楚,但似乎并不完全清楚填充的大小,因此,我将根据我从 C结构包装的遗失艺术中学到的内容添加一个答案 的)

内存对齐(对于struct)

<强>规则:

  • 在每个成员之前,将有一个填充,以便从一个可被其大小整除的地址开始。
    例如,在64位系统上,int应从可被4整除的地址开始,long加8,short加2。
  • charchar[]是特殊的,可以是任何内存地址,因此它们不需要填充。
  • 对于struct,除了每个单独成员的对齐需要之外,整个结构本身的大小将对齐到可被最大单个成员的大小整除的大小,通过末尾填充。
    例如,如果struct的最大成员是long,那么可以被8整除,int然后被4,short再减2。

会员顺序:

  • 成员的顺序可能会影响struct的实际大小,因此请记住这一点。 例如,下面示例中的stu_cstu_d具有相同的成员,但顺序不同,导致2个结构的大小不同。

内存中的地址(用于struct)

<强>规则:

  • 64位系统
    结构地址从(n * 16)个字节开始。 (您可以在下面的示例中看到,结构的所有打印十六进制地址都以0结尾。
    原因:可能最大的单个结构成员是16个字节(long double)。

空白区域

  • 2个结构之间的空格可以由非结构变量使用 例如,在test_struct_address()下面,变量x位于相邻结构gh之间。
    无论是否声明xh的地址都不会更改,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

enter image description here

答案 3 :(得分:20)

结构填料抑制结构填充,对齐最重要时使用的填充,空间最重要时使用的填充。

某些编译器提供#pragma来抑制填充或将其打包为n个字节。有些提供关键字来执行此操作。通常用于修改结构填充的pragma将采用以下格式(取决于编译器):

#pragma pack(n)

例如,ARM提供__packed关键字来抑制结构填充。阅读编译器手册以了解更多相关信息。

所以打包结构是一个没有填充的结构。

将使用通常打包的结构

  • 节省空间

  • 格式化数据结构以通过网络传输一些 协议(当然这不是一个好习惯,因为你需要 处理字节顺序)

答案 4 :(得分:5)

填充和包装只是同一件事的两个方面:

  • 打包或对齐是每个成员四舍五入的大小
  • padding是为匹配对齐而添加的额外空间

mystruct_A中,假设默认对齐为4,每个成员对齐4个字节的倍数。由于char的大小为1,因此ac的填充为4 - 1 = 3个字节,而int b的填充不需要填充,这已经是4个字节。它对mystruct_B的工作方式相同。

答案 5 :(得分:2)

填充规则:

  1. 该结构的每个成员都应位于可被其大小整除的地址。 将填充插入到元素之间或结构的末尾,以确保满足此规则。这样做是为了使硬件更容易,更有效地访问总线。
  2. 结构末尾的填充取决于结构中最大成员的大小。

为什么规则2: 考虑以下结构,

Struct 1

如果我们要为此结构创建一个数组(包含2个结构), 最后无需填充:

Struct1 array

因此,结构的大小= 8个字节

假设我们要创建另一个结构,如下所示:

Struct 2

如果我们要创建此结构的数组, 最后有两种填充字节的可能性。

A。如果我们在末尾添加3个字节并将其对齐为int而不是Long:

Struct2 array aligned to int

B。如果我们在末尾添加7个字节并将其与Long对齐:

Struct2 array aligned to 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)

<块引用>

这些结构是填充的还是压缩的?

它们被填充了。

最初想到的唯一可能是,如果 charint 的大小相同,那么它们的最小大小是char/int/char 结构不允许填充,int/char 结构也是如此。

但是,这需要 sizeof(int)sizeof(char)4(以获得 12 和 8 个尺寸)。整个理论都崩溃了,因为它由标准保证sizeof(char) 总是 1。

如果 charint 的宽度相同,则尺寸应该是一加一,不是四加四。因此,为了获得 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.
};
  1. 结构的第一个成员总是从任何可被结构自身对齐要求整除的地址开始,该要求由最大成员的对齐要求决定(此处为 4int32_t 的对齐)。这与普通变量不同。普通变量可以开始任何可被其对齐整除的地址,但结构的第一个成员不是这种情况。如您所知,结构体的地址与其第一个成员的地址相同。
  2. 结构中可以有额外的填充尾随字节,使下一个结构(或结构数组中的下一个元素)从它自己的地址开始。想想struct st arr[2];。为了使 arr[1]arr[1] 的第一个成员)从地址开始被 4 整除,我们应该在每个结构体的末尾附加 3 个字节。

这是我从The Lost Art of Structure Packing那里学到的。

注意:您可以通过 _Alignof 运算符调查数据类型的对齐要求是什么。此外,您还可以通过 offsetof 宏获取结构体内部成员的偏移量。