Dig into the details of memory alignment

  • 2020-04-01 23:32:58
  • OfStack

1. The introduction

      In a structure, the compiler is each member of the structure Allocates space according to its own natural alignment conditions. Members are stored in memory in the order in which they are declared, and the address of the first member is the same as that of the entire structure.

      For example, the following structure is allocated space for each member (assuming alignment greater than 2 bytes, #pragma pack(n), n = 2,4,8... #pragmapack() is discussed below:


struct test 
{
     char x1;
     short x2;
     float x3;
     char x4;
};

      The first member of the structure, x1, whose offset address is 0, occupies the first byte. The second member x2 is zero Type short, whose starting address must be 2-byte bound , that is, the offset address is a multiple of 2. So the compiler populates a null byte between x2 and x1, placing x2 at the offset address of 2. The third and fourth members of the structure, x3 and x4, just fall on their natural pair addresses, and no additional padding bytes are required in front of them. In the test structure, member x3 requires 4 byte pairs, Is the maximum pair bound unit required of all members of the structure , so the natural pair boundary condition of the test structure is 4 bytes, The size of the entire structure is an integer multiple of the size of the maximum pair bound element (this rule is also followed when there is a structure inside the structure, as mentioned below). , the compiler populates member x4 with three null bytes. The entire structure takes up 12 bytes of space.
      Why do I need memory alignment < (link: #) > . After reading this article, you can understand the following content more easily.

      Okay, so let's talk about #pragma pack:

2. # pragma pack ()

      This preprocessing instruction is used to change the alignment parameters. By default, the C compiler allocates space for each variable or data cell according to its natural pair bound condition. In general, you can change the default alignment parameters by:

        , using the pseudoinstruction #pragma pack (n), the C compiler will align in n bytes.

        , use the pseudoinstruction #pragma pack () to unalign custom bytes.

It can also be written:

# pragma pack (push, n)

# pragma pack (pop)

#pragma pack (n) indicates that the alignment element of each member is not greater than n (n is an integer power of 2). This is defined as an upper bound, which only affects members whose alignment element is greater than n, not members whose alignment byte is greater than n. In fact, from the literal meaning, the word "pack" means "package", #pragma pack(n) provides that n bytes is a "package", personally, if you don't understand it, you can think that the processor can read/write n bytes from memory at one time, which is easy to understand. For a member of size less than n, of course, it's aligned according to its own alignment criteria, because you can pull it out at one time no matter how you put it. For a member whose alignment criteria are greater than n bytes, it takes the same number of reads for the member to be aligned in their own alignment criteria as it does for the member to be aligned in n bytes, but it saves space to align in n bytes, so why not? You can refer to what I mentioned above < (link: #) > . Here's a bull's opinion, the same as mine:

    All it means is that each member of it will require alignment no greater than n.I doesn't mean that each member will have alignment n.otice, after All, It's called pack and not align for a reason-- precisely because it controls packing, not alignment.

In addition, GNU C has the following approach:

        ・ __attribute__ ((n) (aligned)), make the function of structure member aligned on the natural boundary n bytes. If the length of any member in the structure is greater than n, it is aligned according to the length of the largest member.

        , s * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Above n = 1, 2, 4, 8, 16... The first is more common.

3. How do members of a structure find their place

Follow these rules first:

1.   Each member takes the smaller value of its own alignment and the alignment parameter specified by #pragma pack as its own alignment.

2.   The alignment of a complex type (such as a structure) is the alignment used when the type is declared, or the maximum value of the alignment parameter used by all its members when declared, and the minimum value of both of the alignment parameters specified in #pragma pack at this time. Daniel said this:

The documentation for #pragma pack(n) says that "The alignment of a member will be on a boundary that is either a multiple of n or a multiple of The size of The member, tails is smaller". However I Think this is incorrect. The docs should say that the alignment of a member will be on a boundary that is either a multiple of n or the alignment alignment of the member, coin is smaller.

3.   The aligned length must be the largest in the member Align parameters (not the size of the member) Is an integer multiple of, so that when dealing with arrays, you can ensure that each item is bounded.

4.   For arrays, for example, char a[3]; This way, its alignment is the same as writing 3 char's separately. So it's still 1 byte aligned.

              If write: typedef char Array3[3];

              Again, this type of Array3 is aligned by 1 byte, not by its length.

5.   Regardless of the type, the alignment boundary must be 1,2,4,8,16,32,64... One of them.

Here's a simple example:


#pragma pack(8)
struct s1
{
    short a;
    long b;
};
struct s2
{
    char c;
    s1 d;
    long long e;
};
#pragma pack()

      There is one important condition for member alignment: each member is aligned separately. That is, each member is aligned in its own way.

      That is, although the above specifies 8-byte alignment, not all members are 8-byte aligned. The rule of alignment is that each member is aligned by the smaller of the alignment parameters of its type (usually the size of this type) and the one that specifies the alignment parameter (in this case, 8 bytes). And the length of the structure must be an integer multiple of all alignment parameters used (as long as it is an integer multiple of the largest alignment parameter), not enough to fill in the null bytes (depending on the compiler).

      In S1, member a is 2 bytes by default, and the alignment parameter is 8. Member b is 4 bytes, the default is 4 bytes aligned, then 4 bytes aligned, a after 2 bytes to store b, so sizeof(S1) should be 8. 8 is a multiple of 4, which satisfies rule 3 above.

      In S2, c is aligned in 2 bytes like a in S1, and d is a structure, it's 8 bytes, what's it aligned in? For a structure, its default alignment is the largest of the alignment parameters used by all its members when the structure is defined (declared), 4 for S1, less than the specified 8. So member d is 4 byte aligned, c followed by 2 bytes, followed by 8 bytes of structure d. Member e is 8 bytes, it's the default 8 byte alignment, as specified, so it goes to the 8 byte boundary, at this point, 12 bytes are used, so d is followed by 4 bytes, starting at the 16th byte to place member e. At this point, the length is 24 and is already divisible by the maximum alignment parameter 8(member e is aligned in 8 bytes). Thus, a total of 24 bytes are used.

    Isn't that complicated enough? One more:


#pragma pack(4)
struct s1
{
    char a;
    double b;
};
#pragma pack()

#pragma pack(2)
struct s2
{
    char c;
    struct s1 st1;
};
#pragma pack()

 
#pragma pack(2)
struct s3
{
    char a;
    long b;
};
#pragma pack()

#pragma pack(4)
struct s4
{
    char c;
    struct s3 st3;
};
#pragma pack()

      Look first at s1, where a is placed at an offset address of 0 (the first byte). B defaults to 8-byte alignment, but specifies that the alignment parameter is 4 bytes, so b is 4-byte aligned, placed at the offset address of 4, and a is followed by 3 bytes. So sizeof of s1 is 12. The alignment parameter for structure s1 is 4, which is used below.

      S2, c in the first byte. The alignment parameter of st1 itself is 4, but the alignment parameter specified at this time is 2, so st1 is aligned according to 2 bytes, and st1 is stored after c adds a byte. Notice that st1 doesn't change inside, so declare s1 exactly as it is, because we want to make sure sizeof(s2.st1) == sizeof(s1), otherwise it's a mess. So sizeof of s2 is 14. The alignment parameter of structure s2 is 2,14 is an integer multiple of 2.

      Let's look at s3. We put a in the first byte. B defaults to 4-byte alignment, but the alignment parameter specified is 2, so b is 2-byte aligned, placed at the offset address of 2, and a is followed by a byte. Sizeof (s3) is 6. The alignment parameter for structure s3 is 2 (which will be used later), and 6 is an integer multiple of 2.

      Finally, s4, c on the first byte. The alignment parameter of st3 itself is 2, and the alignment parameter specified is 4. Therefore, st3 takes the minimum value, allocates by 2 bytes, and places it at the offset address of 2. C is followed by a byte. Sizeof (s4) is 8, and the alignment parameter of the structure is 2, 8 is an integer multiple of 2.


Related articles: