结构填料和包装

Man*_*anu 189 c struct structure packing padding

考虑:

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

struct mystruct_B
{
   int b;
   char a;
} y;
Run Code Online (Sandbox Code Playgroud)

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

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

什么时候进行填充或包装?

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 )上明确禁止.

  • 这就是为什么,如果你看一下IP,UDP和TCP标题布局,你会看到所有整数字段都是对齐的. (14认同)
  • "失去的C结构包装艺术"解释了填充和包装的提升 - http://www.catb.org/esr/structure-packing/ (14认同)
  • + allyourcode标准保证成员的顺序将被保留,第一个成员将从0偏移开始. (4认同)
  • 第一个成员必须先来吗?我认为arragement完全取决于实现,并且不能依赖(甚至从版本到版本). (3认同)
  • 我想知道:禁止火花上的未对齐内存意味着它无法处理通常的字节数组?我知道结构打包主要用于传输(即联网)数据,当你需要将一个字节数组转换为结构时,并确保一个数组适合结构字段.如果火花不能那样做,那些工作怎么样?! (2认同)

Eri*_*ang 47

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


内存对齐(用于struct)

规则:

  • 在每个单独的成员之前,将有填充,以使其从可被其大小整除的地址开始.
    例如,在64位系统上,C应该从可被4整除的地址开始,并且Go以8 Rust乘2开始.
  • int并且long是特殊的,可以是任何内存地址,因此它们不需要在它们之前填充.
  • 因为short,除了每个单独成员的对齐需要之外,整个结构本身的大小将被对齐到可被最大单个成员的大小整除的大小,通过末端填充.
    例如,如果struct的最大成员char可以被8整除,char[]那么可以被4 整除,struct然后被2 整除.

会员顺序:

  • 成员的顺序可能会影响struct的实际大小,因此请记住这一点.例如longint来自实施例下面具有相同的部件,但以不同的顺序,并导致不同的大小为2层结构.

内存中的地址(用于struct)

规则:

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

空的空间:

  • 2个结构之间的空白空间可以被非结构变量使用,
    例如在(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

在此输入图像描述

  • "规则"实际上非常明确,我无法在任何地方找到直截了当的规则.谢谢. (3认同)
  • @PervezAlam 《&lt;The Lost Art of C Structure Packing&gt;》一书很好地解释了规则,甚至认为它比这个答案长一点。该书可在线免费获取:http://www.catb.org/esr/structure-packing/ (3认同)
  • @PervezAlam 这是一本很短的书,主要关注减少c程序内存占用的技术,最多只需几天即可完成阅读。 (2认同)

Ian*_*anC 41

我知道这个问题很老,这里的大多数答案都很好地解释了填充,但是在我自己试图理解它时,我觉得有一个"视觉"形象正在发生的事情有所帮助.

处理器以确定大小(字)的"块"读取存储器.假设处理器字长8个字节.它会将内存视为8字节构建块的大行.每当它需要从内存中获取一些信息时,它将到达其中一个块并获得它.

变量对齐

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

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

这同样适用于8字节的双精度数,除非它现在必须位于8的存储器地址中,以保证它始终位于块内.

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

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

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

  • 这并不能解释结构打包,但它很好地说明了CPU字对齐. (3认同)
  • @CiroSantilli709 大抓捕六四事件法轮功,这是在gimp上,但我想我可以节省一些时间在油漆上做,哈哈 (2认同)

小智 20

结构填料抑制结构填料,对准最重要时使用的填料,空间最重要时使用的填料.

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

#pragma pack(n)
Run Code Online (Sandbox Code Playgroud)

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

因此,打包结构是没有填充的结构.

通常使用包装结构

  • 节省空间

  • 格式化数据结构以使用某种协议通过网络传输(当然,这不是一个好的做法,因为你需要
    处理字节序)


cas*_*nca 5

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

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

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


jin*_*ong 5

变量存储在可被其对齐方式(通常是其大小)整除的任何地址。因此,填充/打包不仅仅适用于结构。实际上,所有数据都有自己的对齐要求

\n
int 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.\n
Run 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};\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  1. 4结构体的第一个成员始终从可被结构体自身的对齐要求整除的任何地址开始,结构体自身的对齐要求由最大成员的对齐要求(此处为 的对齐int32_t)确定。这与普通变量不同。普通变量可以从任何可被其对齐整除的地址开始,但结构体的第一个成员则不然。如您所知,结构体的地址与其第一个成员的地址相同。
  2. \n
  3. 结构体内部可以有额外的填充尾随字节,使下一个结构体(或结构体数组中的下一个元素)从其自己的地址开始。考虑到struct st arr[2];。为了使arr[1]arr[1]\的第一个成员)从可被4整除的地址开始,我们应该在每个结构体的末尾附加3个字节。
  4. \n
\n

这是我从《失落的结构包装艺术》中学到的。

\n

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

\n