考虑:

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

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

结构尺寸分别为12和8。

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

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


当前回答

填充规则:

结构体的每个成员都应该位于能被其大小整除的地址。 填充在元素之间或结构的末尾插入,以确保满足此规则。这样做是为了硬件更容易和更有效地访问总线。 结构体末尾的填充是根据结构体最大成员的大小决定的。

规则二: 考虑下面的结构,

如果我们要为这个结构体创建一个数组(包含2个结构体), 结束时不需要填充:

因此,struct的大小= 8字节

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

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

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

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

第二个数组的起始地址是8(i)的倍数。e 24)。 struct的大小= 24字节

因此,通过将结构体的下一个数组的起始地址对齐为最大成员(i。E如果我们要创建这个结构体的数组,第二个数组的第一个地址必须从一个地址开始,该地址必须是该结构体最大成员的倍数。这里是24(3 * 8)),我们可以计算出最后所需的填充字节数。

其他回答

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

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)为了正确地从struct的地址启动下一个struct实例,将一些字节追加到每个struct:

// 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.
};

The struct's first member always starts from any addresses divisible by struct's own alignment requirement which is determined by largest member's alignment requirement(here 4, alignment of int32_t). This is different with normal variables. The normal variables can start any addresses divisible by its alignment, but it is not the case for struct's first member. As you know, the address of a struct is the same as the address of its first member. There can be additional padded trailing bytes inside a struct, making next struct(or next element in an array of structs) starting from its own address. Think of struct st arr[2];. To make arr[1](arr[1]'s first member) starting from an address divisible by 4, we should append 3 bytes at the end of each struct.

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

注意:可以通过_Alignof操作符来研究数据类型的对齐要求。同样,你也可以通过offsetof宏来获取结构中成员的偏移量。

这件事没有但是!想要掌握这门学科必须做到以下几点:

细读埃里克·s·雷蒙德所著的《丢失的结构包装艺术》 看一下Eric的代码示例 最后但并非最不重要的是,不要忘记下面关于填充的规则,即结构体的对齐方式与最大类型的对齐方式一致 要求。

(上面的答案解释的很清楚,但是对于padding的大小似乎不是很清楚,所以,我将根据我从The Lost Art of Structure Packing学到的补充一个答案,它已经发展到不局限于C,也适用于Go, Rust。)


内存对齐(用于struct)

规则:

Before each individual member, there will be padding so that to make it start at an address that is divisible by its alignment requirement. E.g., on many systems, an int should start at an address divisible by 4 and a short by 2. char and char[] are special, could be any memory address, so they don't need padding before them. For struct, other than the alignment need for each individual member, the size of whole struct itself will be aligned to a size divisible by strictest alignment requirement of any of its members, by padding at end. E.g., on many systems, if struct's largest member is int then by divisible by 4, if short then by 2.

会员顺序:

成员的顺序可能会影响结构的实际大小,所以要记住这一点。 例如,下面示例中的stu_c和stu_d具有相同的成员,但顺序不同,并导致两个结构体的大小不同。


内存中的地址(用于struct)

空的空间:

两个结构体之间的空白空间可以被适合的非结构变量使用。 例如,在下面的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

填充将结构成员对齐到“自然”地址边界——例如,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__((__packked__)),因此如下:

struct __attribute__((__packed__)) mystruct_A {
    char a;
    int b;
    char c;
};

会在32位架构上产生大小为6的结构。

不过需要注意的是,在允许未对齐内存访问的体系结构(如x86和amd64)上,未对齐内存访问速度较慢,并且在严格对齐的体系结构(如SPARC)上是明确禁止的。

Data structure alignment is the way data is arranged and accessed in computer memory. It consists of two separate but related issues: data alignment and data structure padding. When a modern computer reads from or writes to a memory address, it will do this in word sized chunks (e.g. 4 byte chunks on a 32-bit system) or larger. Data alignment means putting the data at a memory address equal to some multiple of the word size, which increases the system’s performance due to the way the CPU handles memory. To align the data, it may be necessary to insert some meaningless bytes between the end of the last data structure and the start of the next, which is data structure padding.

In order to align the data in memory, one or more empty bytes (addresses) are inserted (or left empty) between memory addresses which are allocated for other structure members while memory allocation. This concept is called structure padding. Architecture of a computer processor is such a way that it can read 1 word (4 byte in 32 bit processor) from memory at a time. To make use of this advantage of processor, data are always aligned as 4 bytes package which leads to insert empty addresses between other member’s address. Because of this structure padding concept in C, size of the structure is always not same as what we think.