This article was originally published on 2014-07-21 15:32:28

1. The introduction

Consider the following structure definition:

typedef struct{
    char  c1;
    short s; 
    char  c2; 
    int   i;
}T_FOO;
Copy the code

Assuming that the members of this structure are compact in memory and that C1 starts at address 0, the address of S is 1, the address of C2 is 3, and the address of I is 4.

Now, let’s write a simple program:

int main(void){  
    T_FOO a; 
    printf("c1 -> %d, s -> %d, c2 -> %d, i -> %d\n", 
          (unsigned int) (void*)&a.c1 - (unsigned int) (void*)&a,
          (unsigned int) (void*)&a.s  - (unsigned int) (void*)&a, 
          (unsigned int) (void*)&a.c2 - (unsigned int) (void*)&a, 
          (unsigned int) (void*)&a.i  - (unsigned int) (void*)&a); 
    return 0;
}
Copy the code

Output after running:

c1 -> 0, s -> 2, c2 -> 4, i -> 8
Copy the code

Why is that? This is the problem with byte alignment.

This article introduces common byte alignment problems in detail on the basis of many references. As written earlier, most of the sources are not available for examination, please forgive me.

2. What is byte alignment

In modern computer memory space according to the byte, theoretically can from any starting address access any type of variable, but in fact, during a visit to a specific type variable in a particular memory address access frequently, which requires all kinds of data according to certain rules in space arrangement, rather than one by one order to deposit, this is the alignment.

3. Reasons and functions of alignment

  1. Different hardware platforms treat storage space differently. Some platforms only allow certain types of data to be accessed from certain locations, rather than allowing it to be stored arbitrarily in memory. For example, the Motorola 68000 processor does not allow 16-bit words to be stored at odd addresses, which would trigger an exception, so byte alignment must be guaranteed when programming in this architecture.
  2. If data storage is not aligned according to platform requirements, access efficiency will be lost. For example, 32-bit Intel processors access (both read and write) memory data through a bus. Each bus cycle accesses 32-bit memory data, which is stored in bytes, starting at even addresses. If a 32-bit piece of data is not stored at a memory address divisible by 4 bytes, then the processor needs 2 bus cycles to access it, which is obviously much less efficient. Therefore, access efficiency can be improved through proper memory alignment. To enable the CPU to access the data quickly, the starting address of the data should have an “alignment” feature. For example, the start address of 4-byte data should be on the 4-byte boundary, that is, the start address is divisible by 4.
  3. Proper use of byte alignment can also significantly save storage space. Note, however, that using 1 – or 2-byte alignment on a 32-bit machine can slow down variable access, so,Processor types need to be considered. At the same time,The type of compiler should also be considered.The default is 4-byte alignment in both VC/C++ and GNU GCC.

4. Classification and criteria for alignment

This section introduces structure alignment and stack memory alignment based on the Intel X86 architecture. Bitfields are essentially structure types.

For Intel X86 platforms, each memory allocation should start with an integer multiple of 4, whether for structural variables or simple types.

4.1. Structure alignment

In C, a structure is a compound data type whose constituent elements can be either variables of the basic data types (int, LONG, float, etc.) or data units of some compound data types (array, structure, union, etc.). The compiler allocates space for each member of a structure in alignment with its natural boundaries. Members are stored in memory in the order in which they are declared, with the address of the first member being the same as the address of the entire structure.

The problem with byte alignment is mostly with structures.

4.1.1. Simple examples

Let’s start with a simple example (32-bit, X86 processor, GCC compiler) :

Suppose the structure is defined as follows:

struct A{
    int    a;
    char   b;
    short  c;
};

struct B{
    char   b;
    int    a;
    short  c;
};
Copy the code

The length of each data type on a 32-bit machine is known to be 1 byte char, 2 byte short, 4 byte int, 4 byte long, 4 byte float and 8 byte double. So what are the sizes of these two structures?

Sizeof (strcut A) is 8; Sizeof (struct B) is 12.

Structs A and B contain A 4-byte int, A 1-byte CHAR, and A 2-byte short, just in different order. A and B should be 7 bytes each, because the compiler needs to align the data members spatially.

4.1.2. Alignment criteria

Let’s start with four important basic concepts:

  1. Alignment values for data types themselves: char data is aligned to 1 byte, short data to 2 bytes, int/float to 4 bytes, and double to 8 bytes.

  2. Self-aligned value of a structure or class: the value of its members with the largest self-aligned value.

  3. Specify alignment values: #pragma pack (value) specifies alignment values value.

  4. Valid aligned values for data members, structs, and classes: the smaller of the self-aligned value and the specified aligned value, i.e., valid aligned value =min{self-aligned value, currently specified pack value}.

Based on these principles, it is easy to discuss the alignment of the members of a specific data structure with themselves.

Where the valid alignment value N is the value ultimately used to determine where the data is stored. Valid alignment value N indicates “alignment on N”, that is, the starting address for storing the data % N = 0. Data variables in a data structure are stored in a defined order. The starting address of the first data variable is the starting address of the data structure. The structure’s member variables are stored in alignment, and the structure itself is rounded according to its own valid alignment value (i.e., the total length of the structure’s member variables is an integer multiple of the valid alignment value of the structure).

Analysis of structure B in Section 3.1.1:

Suppose B is stored from address space 0x0000 and specifies an alignment value of 4(4-byte alignment) by default. The alignment value of member variable B is 1, which is smaller than the default alignment value of 4, so its valid alignment value is 1, and its storage address 0x0000 conforms to 0x0000%1=0.

The alignment value of member variable A itself is 4, so the valid alignment value is also 4, and can only be stored in four consecutive byte Spaces starting from 0x0004 to 0x0007, conforming to 0x0004%4=0 and adjacent to the first variable.

The alignment value of variable C itself is 2, so the effective alignment value is also 2, which can be stored in two byte space 0x0008~0x0009, conforming to 0x0008%2=0.

So the contents from 0x0000 to 0x0009 are B.

The self-alignment value of the data structure B is the largest alignment value of its variables (in this case B), which is 4, so the effective alignment value of the structure is also 4. 0x0000~0x0009=10 bytes, (10+2) % 4 = 0.

So 0x0000A~0x000B is also occupied by structure B. Sizeof (struct B)=12

The reason why the compiler adds two bytes is to achieve efficient access to structured arrays. Imagine defining an array of structure B. The first structure starts at address 0, but what about the second structure?

By definition, all the elements in an array are right next to each other. If we do not add the structure size to an integer multiple of 4, the next structure will start at 0x0000A, which clearly does not satisfy the address alignment of the structure. Therefore, complement the structure to integer multiples of the valid alignment size.

Actually to char/short/int/float/double existing types of alignment value itself is also considered, based on the array just because these types of length is known, so their own alignment values are known.

The above concepts are easy to understand, but I prefer the following alignment guidelines.

The details of struct byte alignment depend on the specific compiler implementation, but in general three guidelines are met:

  1. The first address of a structure variable is divisible by the size of its widest primitive type member.
  2. Each member’s offset is an integer multiple of its size. If necessary, the compiler adds internal adding bytes between the members.
  3. The total size of the structure is an integer multiple of the size of the structure’s widest base-type member, and the compiler adds the padding byte {trailing padding} after the last member if necessary.

The above rules are explained as follows:

  • First, when the compiler makes space for a structure, it first looks for the widest base datatype in the structure, and then looks for the location of the memory address divisible by the base datatype as the first address of the structure.Use the size of this widest base data type as the alignment modulus described above.
  • Rule two: Before opening space for a member of the structure,The compiler first checks whether the offset of the head address of the precleared space relative to the head address of the structure is an integer multiple of the size of the memberIf, it stores the current member, otherwise, it fills certain bytes between the current member and the previous member to achieve the integer multiple requirement, that is, the first address of the pre-space is moved a few bytes later.
  • Article 3:The total structure size is including padding bytes, the last member must satisfy the third in addition to the above two, or a few bytes must be filled in at the end to satisfy this requirement.

Assuming 4-byte alignment, what is the output of the following program?

/* The OFFSET macro defines the internal OFFSET of a member of a given structure */
#define OFFSET(st, field) (size_t)&(((st*)0)->field)
typedef struct{
    char  a;
    short b;
    char  c;
    int   d;
    char  e[3];
}T_Test;
 
int main(void){  
    printf("Size = %d\n a-%d, b-%d, c-%d, d-%d\n e[0]-%d, e[1]-%d, e[2]-%d\n".sizeof(T_Test), OFFSET(T_Test, a), OFFSET(T_Test, b),
           OFFSET(T_Test, c), OFFSET(T_Test, d), OFFSET(T_Test, e[0]),
           OFFSET(T_Test, e[1]),OFFSET(T_Test, e[2]));
    return 0;
}
Copy the code

The following output is displayed:

Size = 16
a-0, b-2, c-4, d-8
e[0]-12, e[1]-13, e[2]-14
Copy the code

Here is a concrete analysis:

First, char A takes 1 byte, no problem.

Short B itself takes up 2 bytes, and according to rule 2 above, one byte needs to be filled between B and A.

Char C takes 1 byte, no problem.

Int d itself takes 4 bytes, and according to rule 2, 3 bytes need to be filled between d and c.

Char e [3]; It occupies 3 bytes. According to rule 3, a byte must be added after it.

Therefore, sizeof(T_Test) = 1 + 1 + 2 + 1 + 3 + 4 + 3 + 1 = 16 bytes.

4.1.3. Hidden dangers of alignment

4.1.3.1. Data type conversion

Many of the pitfalls of alignment in your code are implicit. For example, when casting:

int main(void){  
    unsigned int i = 0x12345678;
    unsigned char *p = (unsigned char *)&i;
    *p = 0x00;
    unsigned short *p1 = (unsigned short *)(p+1);
    *p1 = 0x0000;
    return 0;
}
Copy the code

The last two lines of code, accessing an unsigned short variable from an odd-numbered boundary, are clearly not aligned. On X86, similar operations only affect efficiency; On MIPS or SPARC, however, this can result in an error because they require byte alignment.

For struct B in Section 3.1.1, define the following function:

void Func(struct B *p){
     //Code
}
Copy the code

An exception is likely if p->a is accessed directly within the function body. Because MIPS thinks that a is int, its address should be a multiple of 4, but the address of p->a is probably not a multiple of 4.

A problem can occur if the address of p is not on the aligned boundary, such as when P comes from a cross-CPU packet (multiple data types are transmitted sequentially in a single packet), or when P is computed by pointer shift. Special attention should therefore be paid to the handling of interface input data by interface functions across CPU data, as well as to the security of access when pointer shifts are then cast into structural Pointers.

Solutions are as follows:

  1. Defines a local variable to this structure, usingmemmoveTo copy the data in.
void Func(struct B *p){
    struct B tData;
    memmove(&tData, p, sizeof(struct B));
     // Tdata. a can then be safely accessed because the compiler has assigned tData to the correct starting address
}
Copy the code

Note: If you can determine that the starting address of p is ok, you do not need to do this; If there is uncertainty (such as data input across cpus, or data computed by pointer shifts), you need to do this. 2. Define STRUCT_T with 1 byte alignment using #pragma pack (1).

4.1.3.2. Data communication between processors

When processors communicate via messages (structures in the case of C/C++), there are issues of byte alignment and byte order.

Most compilers provide some memory options for the user to use. This allows the user to choose a different byte alignment depending on the processor. For example, the C/C++ compiler provides #pragma pack(n) n= 1,2,4, etc., which allows the compiler to generate object files with memory data arranged in a specified way at memory addresses divisible by 1,2,4 bytes.

However, byte alignment can cause changes in message structure length across different compilation platforms or processors. The compiler may populate the message structure for byte alignment, and different compilation platforms may populate it in different forms, greatly increasing the risk of data communication between processors.

The following uses a 32-bit processor as an example to propose a memory alignment method to solve the above problems.

For locally used data structures, 4-byte alignment is adopted to improve memory access efficiency. At the same time, in order to reduce the memory cost, the structure members are reasonably arranged to reduce the gap between the members caused by 4-byte alignment and reduce the memory cost.

For the data structure between processors, it is necessary to ensure that the message length will not change due to different compilation platforms or processors, and use 1-byte alignment to tighten the message structure. To ensure the memory access efficiency of the message data structure between processors, the message members are aligned with 4 bytes by byte padding.

Member location of data structure should consider the relationship between members, data access efficiency and space utilization. The order rules are: the 4-byte is first, the 2-byte is next to the last 4-byte member, the 1-byte is next to the last 2-byte member, and the fill byte is last.

Examples are as follows:

typedef struct tag_T_MSG{
    long  ParaA;
    long  ParaB;
    shortParaC.char  ParaD;
    char  Pad;   // Fill bytes
}T_MSG;
Copy the code

4.1.3.3. Troubleshoot alignment problems

If alignment or assignment problems occur, check:

  1. Compiler byte order size-side setting;
  2. Whether the processor architecture itself supports unaligned access;

If yes, see if alignment is set.

If not, see if the access needs to be decorated to indicate that it has a special access operation.

4.1.4. Change alignment

Basically, change the default byte alignment of the C compiler.

By default, the C compiler allocates space for each variable or data unit according to its natural pair bound condition. In general, the default bound condition can be changed by:

  • usePragma pack(n)The: C compiler will align by n bytes;
  • usePragma pack(): Cancels custom byte alignment.

Alternatively, there is the following option (GCC specific syntax) :

  • __attribute__((aligned (n))): aligns the applied structure members on the n-byte natural boundary. If there are members in the structure whose length is greater than n, align them according to the length of the largest member.
  • __attribute__((packed)): Cancels the optimized alignment of structures during compilation and aligns structures according to the actual number of bytes.

Note:

An important feature of GCC is the __attribute__ mechanism, which allows you to set Function attributes, Variable attributes, and Type attributes.

Alignment values can be dynamically modified using #pragma Pack when coding. See Section 5.3 of Appendix for specific syntax instructions.

Custom aligned values should be restored using #pragma Pack (), otherwise it will affect subsequent structures.

Analyze the following structure C:

#pragma pack(2)  // Specify 2-byte alignment
struct C{
    char  b;
    int   a;
    short c;
};
#pragma pack()   // Cancel the alignment and restore the default alignment
Copy the code

The alignment value of variable B itself is 1, and the alignment value specified is 2, so the effective alignment value is 1. Assuming that C starts at 0x0000, b is stored at 0x0000, conforming to 0x0000%1=0.

The alignment value of variable A itself is 4, and the alignment value is specified as 2, so the valid alignment value is 2. The alignment value is stored in four consecutive bytes 0x0002~0x0005, conforming to 0x0002%2=0.

The self-alignment value of variable C is 2, so the valid alignment value is 2, and it is stored in the sequence 0x0006~0x0007, conforming to 0x0006%2=0.

So the total 8 bytes from 0x0000 to 0x00007 store the variable C.

C has a self-alignment value of 4, so its effective alignment value is 2. 8%2=0, C occupies only eight bytes from 0x0000 to 0x0007. So sizeof(struct C)=8.

Note: The number of bytes the structure aligns to is not entirely dependent on the current specified pack value, for example:

#pragma pack(8)
struct D{
    char  b;
    short a;
    char  c;
};
#pragma pack()
Copy the code

Although #pragma pack(8), it is still aligned to 2 bytes, so sizeof(struct D) is 6. So, the number of bytes aligned =min {currently specified pack value, maximum member size}.

In addition, 1-byte alignment can be written to the following form in the GNU GCC compiler:

#define GNUC_PACKED __attribute__((packed))
struct C{
    char  b;
    int   a;
    short c;
}GNUC_PACKED;
Copy the code

Sizeof (struct C) is 7.

4.2. Stack memory alignment

In VC/C++, stack alignment is not affected by struct member alignment options and always remains aligned on 4-byte boundaries.

Stack memory alignment:

#pragma pack(push, 1)  // 1, 2, 4, 8
struct StrtE{
    char m1;
    long m2;
};
#pragma pack(pop)
 
int main(void){  
    char a;
    short b;
    int c;
    double d[2];
    struct StrtE s;
        
    printf("a address: %p\n", &a);
    printf("b address: %p\n", &b);
    printf("c address: %p\n", &c);
    printf("d[0] address: %p\n", &(d[0]));
    printf("d[1] address: %p\n", &(d[1]));
    printf("s address: %p\n", &s);
    printf("s.m2 address: %p\n", &(s.m2));
    return 0;
}
Copy the code

The results are as follows:

a    address:   0xbfc4cfff
b    address:   0xbfc4cffc
c    address:   0xbfc4cff8
d[0] address:   0xbfc4cfe8
d[1] address:   0xbfc4cff0
s    address:   0xbfc4cfe3
s.m2 address:   0xbfc4cfe4
Copy the code

As you can see, both are aligned to 4 bytes, and the preceding char and short are not joined together, which is different from what is done in the structure body.

As for why the output address value is smaller, it is because the stack on this platform “grows” backwards.

4.3. Bit-field alignment

4.3.1. Bit-field definition

Some information does not need to occupy a complete byte, but only a few or a binary bit. For example, when storing a switch quantity, there are only 0 and 1 states, and a binary can be used. To save storage space and simplify processing, C provides a data structure called a bit field or bit segment.

A bitfield is a special structure member or union member (that is, only used in a structure or union) that specifies the number of bits that the member occupies when it is stored in memory, thereby representing data more compact within the machine. Each bit-field has a domain name, allowing the corresponding bits to be manipulated by the domain name in the program, so that several different objects can be represented by a bit-field of one byte.

Bit-field definitions are similar to structure definitions and have the form:

structBit-field structure name {Bit-field list};Copy the code

Where the bit field list is in the form of:

Type specifier bit domain name: bit field lengthCopy the code

The use of bit-fields is the same as the use of structural members, and its general form is:

Bit-domain variable name. Bit-domain nameCopy the code

Bitfields allow output in a variety of formats.

A bitfield is essentially a type of structure, but its members are binary assigned. Bit-field variables are described in the same way as structural variables, which can be defined before, at the same time or directly.

Bit-fields are mainly used in the following two situations:

  1. When the machine has less available memory space and the use of bit-fields can save a lot of memory. For example, when a structure is an element of a large array.
  2. When a structure or association needs to be mapped to a predetermined organizational structure. For example, when you need to access a specific bit in a byte.

4.3.2. Alignment criteria

Bit-field members cannot be sizeof individually. The following focuses on sizeof structures that contain bitfields.

C99 specifies that int, unsigned int, and bool can be bit-field types, but the compiler almost always extends this to allow other types. As a very common programming tool in embedded systems, bit domain has the advantage of reducing the storage space of programs.

Its alignment rules are roughly as follows:

  1. If adjacent bit-field fields are of the same type and the sum of their bitwidths is less than the sizeof the type, the following fields will be stored next to the previous one until it cannot accommodate them.
  2. If adjacent bit-field fields are of the same type, but the sum of their bitwidths is greater than sizeof the type, the following fields will start from the new storage unit and have an offset that is an integer multiple of their type size.
  3. If the type of the adjacent bit-field field is different, the specific implementation of each compiler is different. VC6 adopts the uncompressed mode, while dev-C ++ and GCC adopt the compressed mode.
  4. If bit-field fields are interspersed with non-bit-field fields, no compression is performed.
  5. The total size of the entire structure is an integer multiple of the size of the widest primitive type member, and the bitfields are aligned by the number of bytes of their widest type.

【 case 5 】

struct BitField{
    char element1  : 1;
    char element2  : 4;
    char element3  : 5;
};
Copy the code

The bitfield type is char, and the first byte only holds element1 and element2, so element1 and element2 are compressed into the first byte, while element3 can only start from the next byte. So sizeof(BitField) is 2.

[6]

struct BitField1{
    char element1   : 1;
    short element2  : 5;
    char element3   : 7;
};
Copy the code

Due to the different types of adjacent bitfields, its sizeof is 6 in VC6 and 2 in dev-C ++.

[example 7]

struct BitField2{
    char element1  : 3;
    char element2  ;
    char element3  : 5;
};
Copy the code

Non-bit-field fields are interwoven, do not compress, and have a size of 3 in both VC6 and Dev-C++.

[example 8]

struct StructBitField{
    int element1   : 1;
    int element2   : 5;
    int element3   : 29;
    int element4   : 6;
    char element5  :2;
    char stelement;  It is also possible to specify ordinary members in bit-domain structures or unions
};
Copy the code

The widest type int in the bitfield has 4 bytes, so the structure is aligned with 4 bytes and has sizeof 16 in VC6.

4.3.3. Precautions

There are a few things to note about bit-field operations:

1) The address of the bit-field is not accessible, so the & operator is not allowed for the bit-field. You cannot use Pointers to bitfields or arrays of bitfields (arrays are special Pointers). For example, the scanf function cannot store data directly into a bitfield:

int main(void){  
    struct BitField1 tBit;
    scanf("%d", &tBit.element2); //error: cannot take address of bit-field 'element2'
    return 0;
}
Copy the code

The scanf function reads the input into a plain integer variable and assigns it to tbit.element2.

2) Bitfields cannot be returned as a result of a function.

3) The bitfield is in units of the defined type, and the length of the bitfield cannot exceed the length of the defined type. For example, int a:33 is disallowed.

4) The bit domain can not specify the bit domain name, but cannot access the nameless bit domain.

Bitfields can be filled or repositioned without bitwise domain names, depending on the type. For example, char :0 means that the entire bit field is pushed back by one byte, that is, the next bit field after the nameless bit field is stored from the next byte. Similarly, short :0 and int :0 mean that the entire bit field is pushed back by two and four bytes, respectively.

When the length of the vacancy field is a specific value N (as in int :2), this variable is used only as a placeholder for N bits.

[example 9]

struct BitField3{
    char element1  : 3;
    char  :6;
    char element3  : 5;
};
Copy the code

The structure size is 3. Because element1 is three bits, six bits are reserved and char is eight bits, the six bits reserved can only be placed in the second byte. Again, element3 can only be placed in byte 3.

struct BitField4{
    char element1  : 3;
    char  :0;
    char element3  : 5;
};
Copy the code

A bit-field of length 0 tells the compiler to place the next bit-field at the beginning of a storage location. As shown above, the compiler allocates 3 bits to member element1, then skips the remaining 4 bits to the next storage location, and allocates 5 bits to member element3. So, the structure above has size 2.

5) Range of representation of bit field:

  • Bit-fields cannot be assigned more than they can represent.
  • The type of bitfield determines the result of the value that the encoding can represent.

For the second, if the bitfield is of unsigned type, it is directly converted to a positive number. If it is not an unsigned type, it determines whether the highest bit is 1. If it is 1, it represents the complement code. Then all bits except the sign bit are reversed and one is added to obtain the final result data (source code). Such as:

unsigned int p:3 = 111;   / / p says 7
int p:3 = 111;            // all bits except the sign bit are inverted and plus one
Copy the code

The way each bit field is stored in memory depends on the compiler, and can be stored either left to right or right to left.

Execute the following code under VC6:

int main(void){  
    union{
        int i;
        struct{
            char a : 1;
            char b : 1;
            char c : 2;
        }bits;
    }num;
 
    printf("Input an integer for i(0~15): ");
    scanf("%d", &num.i);
    printf("i = %d, cba = %d %d %d\n", num.i, num.bits.c, num.bits.b, num.bits.a); 
    return 0;
Copy the code

If the input I value is 11, the output is I = 11 and CBA = -2-1-1.

Intel x86 processors store data in small byte order, so the bit fields in bits are placed in memory in ccBA order. When num. I is set to 11, the least significant bit (field A) of bits is 1, and a, B, and C are stored in binary format from the lowest address to the highest address.

But why is the final print a=-1 instead of 1?

Because the type signed char defined by bitfield A is a signed number, a sign extension is performed even though a has only 1 bit. 1 exists as a complement, corresponding to the original code -1.

If the type of a, b, and C is set to unsigned char, then cba = 2 1 1 1011 is the binary number of 11.

Note: In C, a data construction type where different members use a common storage area is called a union (or Commons). The amount of space taken up by the union depends on the largest member of the type. Unions are similar to structures in definition, specification, and usage.

7) The implementation of bitfields varies from compiler to compiler, and the use of bitfields affects program portability. Therefore, it is best not to use bitfields unless necessary.

8) Although using bitfields saves memory space, it increases processing time. When accessing individual bit-field members, the bit-field needs to be decomposed from the word in which it resides or conversely a value compressed into the bit in which the bit-field resides.

5. To summarize

Let’s go back to the introductory question.

By default, the C/C++ compiler aligns structure, stack member data in memory by default. Thus, the introductory program output becomes “C1 -> 0, S -> 2, C2 -> 4, I -> 8”.

The compiler moves the unaligned members back, aligning each member to a natural boundary, which also causes the size of the entire structure to increase. Although a bit of space is sacrificed (there are voids between members), performance is improved.

For this reason, sizeof(T_ FOO) in the introductory example is 12, not 8.

In a structure, consider the variables themselves and the specified alignment values; On the stack, regardless of the size of the variable itself, uniform alignment to 4 bytes.

Note: This article is reprinted from www.cnblogs.com/clover-toei…


Welcome to follow my wechat public number [database kernel] : share mainstream open source database and storage engine related technology.

The title The url
GitHub dbkernel.github.io
zhihu www.zhihu.com/people/dbke…
SegmentFault segmentfault.com/u/dbkernel
The Denver nuggets Juejin. Im/user / 5 e9d3e…
OsChina my.oschina.net/dbkernel
CNBlogs www.cnblogs.com/dbkernel