Recently, we encountered a problem when we were working on a project. Threadx on ARM used message queue to deliver messages in communication with DSP (the final implementation principle was interrupt + shared memory). In the actual operation process, we found that ThreadX always crashed, so after troubleshooting, Because the structure that delivers the message does not take byte alignment into account.

With sorting out the C language byte alignment problem to share with you.

A concept,

Alignment is related to the location of the data in memory. A variable is said to be naturally aligned if its memory address is an integer multiple of its length. For example, on a 32-bit CPU, if the address of an integer variable is 0x00000004, it is naturally aligned.

First, what bit, byte, word

The name of the The English name meaning
position bit A binary bit is called a bit
byte Byte Eight binary bits are called a Byte
word word A fixed length of transactions used by a computer to process at one time

Word length

The number of bits in a word. Modern computer words are usually 16,32, 64 bits long. (The normal word length for an n-bit system is N/8 bytes.)

Different cpus can process different bits of data at a time. A 32-bit CPU can process 32 bits of data at a time. A 64-bit CPU can process 64 bits of data at a time.

And word length, we sometimes call it a word. In a 16-bit CPU, a word is exactly two bytes, while in a 32-bit CPU, a word is four bytes. If the word is the unit, there are double words (two words), four words (four words) up.

Two, alignment rules

For standard data types, the address should only be an integer multiple of its length. For non-standard data types, the following principles are used: Array: Align according to the basic data type. If the first one is aligned, the rest will be aligned. Union: Aligns to the data type with the largest length it contains. Structures: Each data type in a structure should be aligned.

How to limit the number of fixed byte alignment bits?

1. The default

By default, the C compiler allocates space for each variable or unit of data according to its natural bound conditions. In general, you can change the default bound conditions by:

2. #pragma pack(n)

· Using the directive #pragma pack (n), the C compiler will be aligned to n bytes. · Use #pragma pack () to cancel custom byte alignment.

#pragma pack(n) is used to set variables to n-byte alignment. N-byte alignment means that the offset from the starting address of the variable can be stored in one of two cases:

  1. If n is greater than or equal to the number of bytes occupied by the variable, then the offset must meet the default alignment
  2. If n is less than the number of bytes occupied by the variable’s type, then the offset is a multiple of n and does not meet the default alignment.

The total size of the structure also has a constraint: if n is greater than or equal to the number of bytes occupied by all member variable types, then the total size of the structure must be a multiple of the number of bytes occupied by the largest variable; Otherwise it has to be a multiple of n.

3. __attribute

In addition, there is the following method: · __attribute((aligned (n))), so that the members of the structure in question are aligned on the n-byte natural boundary. If there are members in the structure whose length is greater than n, align with the length of the largest member. · Attribute ((Packed)), which removes optimized alignment of structures during compilation and aligns them according to the actual number of bytes consumed.

3. Assembly. The align

Assembly code usually uses.align to specify byte aligned bits.

Align: specifies the alignment of the data in the following format:

.align [absexpr1, absexpr2]
Copy the code

Fills unused storage areas with values in some alignment. The first value indicates the alignment,4, 8,16, or 32. The second expression value indicates the padding value.

4. Why align?

Instead of accessing memory byte by byte, the operating system accesses memory by word lengths like 2, 4, and 8. Therefore, when the CPU reads data from memory to register, the data length of the IO is usually word length. For example, the granularity of access is 4 bytes for a 32-bit system and 8 bytes for a 64-bit system. When the length of the data being accessed is n bytes and the data address is n bytes aligned, the operating system can efficiently locate the data at one time without having to read it multiple times and handle additional operations such as alignment. Data structures should be aligned on natural boundaries as much as possible. If unaligned memory is accessed, the CPU needs to make two memory accesses.

Potential pitfalls with byte alignment:

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

unsigned int i = 0x12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;

p=&i;
*p=0x00;
p1=(unsigned short *)(p+1);
*p1=0x0000;
Copy the code

The last two lines of code, which access an unsignedshort variable from an odd number boundary, clearly do not conform to alignment rules. On x86, a similar operation would only affect efficiency, but on MIPS or Sparc, it would be an error because they require byte alignment.

Five, for example,

Example example 1: The number of bytes occupied by the OS basic data type

First look at the number of bits in the operating systemTo view the number of bytes used by basic data types on 64-bit operating systems:

#include <stdio.h>

int main(a)
{
    printf("sizeof(char) = %ld\n".sizeof(char));
    printf("sizeof(int) = %ld\n".sizeof(int));
    printf("sizeof(float) = %ld\n".sizeof(float));
    printf("sizeof(long) = %ld\n".sizeof(long));                                      
    printf("sizeof(long long) = %ld\n".sizeof(long long));
    printf("sizeof(double) = %ld\n".sizeof(double));
    return 0;
}
Copy the code

Example 2: Size of memory used by structures — the default rule

Consider the number of bits occupied by the following structure

struct yikou_s
{
    double d;
    char c;
    int i;
} yikou_t;
Copy the code

The execution result

sizeof(yikou_t) = 16
Copy the code

The positions of variables in the content are as follows:

The position of member C is also affected by the byte order, some may be in position 8

The compiler gives us memory alignment so that the starting address of each member variable must be offset by a multiple of the number of bytes occupied by the variable type, and the size of the structure must be a multiple of the number of bytes occupied by the type with the largest space in the structure.

For offsets: The offset from the start address of the variable type n to the start address of the structure must be a multiple of sizeof(type(n))

Char: the offset must be sizeof(char), which is a multiple of 1. Int: The offset must be sizeof(int), which is a multiple of 4float: The offset must be sizeof(floatThe offset must be sizeof(double), which is a multiple of 8Copy the code

Example 3: Resize a structure

We adjust the positions of variables in the structure as follows:

struct yikou_s
{
    char c;
    double d;
    int i;
} yikou_t;
Copy the code

The execution result

sizeof(yikou_t) = 24
Copy the code

The variables are laid out in memory as follows:

When there are nested conforming members in the structure, the offset of the compound member relative to the first address of the structure is an integer multiple of the size of the widest basic type of the compound member.

Example 4: #pragma pack(4)

#pragma pack(4)

struct yikou_s
{
    char c;
    double d;
    int i;
} yikou_t;
Copy the code
sizeof(yikou_t) = 16
Copy the code

Example 5: #pragma pack(8)

#pragma pack(8)

struct yikou_s
{
    char c;
    double d;
    int i;
} yikou_t;
Copy the code
sizeof(yikou_t) = 24
Copy the code

Example 6: Assembly code

For example, the following are the entry position codes of exception vectors IRQ and FIQ in the intercepted Uboot code:

Six, summary strength

For those of you with lazy hands, just post a complete example:

#include <stdio.h>
main()
{
struct A {
    int a;
    char b;
    short c;
};
 
struct B {
    char b;
    int a;
    short c;
};
struct AA {
   // int a;
    char b;
    short c;
};

struct BB {
    char b;
   // int a;
    short c;
}; 
#pragma pack (2) /* Specifies alignment by 2 bytes */
struct C {
    char b;
    int a;
    short c;
};
#pragma pack () /* Unalign and restore the default alignment */
 
 
 
#pragma pack (1) /* Specifies 1 byte alignment */
struct D {
    char b;
    int a;
    short c;
};
#pragma pack ()/* Unalign and restore the default alignment */
 
int s1=sizeof(struct A);
int s2=sizeof(struct AA);
int s3=sizeof(struct B);
int s4=sizeof(struct BB);
int s5=sizeof(struct C);
int s6=sizeof(struct D);
printf("%d\n",s1);
printf("%d\n",s2);
printf("%d\n",s3);
printf("%d\n",s4);
printf("%d\n",s5);
printf("%d\n",s6);
}
Copy the code