Understanding Byte Alignment in Linux

Recently, I encountered a problem where threadx running on ARM crashed when communicating with DSP using message queues (the underlying implementation is through interrupts and shared memory). During the troubleshooting process, it was found that the structure used for message passing did not consider the issue of byte alignment.

Here, I will整理一下 the issue of byte alignment in C language and share it with everyone.

1. Concept

Alignment is related to the position of data in memory. If the memory address of a variable is exactly a multiple of its length, it is called naturally aligned. For example, on a 32-bit CPU, if an integer variable’s address is 0x00000004, it is naturally aligned.

First, let’s understand bits, bytes, and words.

Name English Name Meaning
Bit bit 1 binary digit is called 1 bit
Byte Byte 8 binary digits are called 1 Byte
Word word A fixed length that the computer uses to process transactions at one time

Word Length

The number of bits in a word; modern computers typically have word lengths of 16, 32, or 64 bits. (Generally, the word length of an N-bit system is N/8 bytes.)

Different CPUs can process different numbers of bits at a time; a 32-bit CPU can process 32 bits of data at once, while a 64-bit CPU can process 64 bits of data. Here, the bit refers to the word length.

The so-called word length is sometimes referred to as word. In a 16-bit CPU, a word is exactly two bytes, while in a 32-bit CPU, a word is four bytes. If we use the word as a unit, there are also double words (two words) and quad words (four words).

2. Alignment Rules

For standard data types, the address only needs to be a multiple of its length. For non-standard data types, alignment is done according to the following principles: Arrays: Align according to the basic data type; once the first is aligned, the subsequent ones are naturally aligned. Unions: Align according to the maximum length of the data type contained. Structures: Each data type in the structure must be aligned.

3. How to Control Byte Alignment?

1. Default

By default, the C compiler allocates space for each variable or data unit according to its natural alignment conditions. Generally, the following methods can be used to change the default alignment conditions:

2. #pragma pack(n)

· Use the pseudo-instruction #pragma pack(n), the C compiler will align according to n bytes. · Use the pseudo-instruction #pragma pack(), to cancel the custom byte alignment method.

#pragma pack(n) is used to set variables to align by n bytes. n-byte alignment means that the offset of the starting address of the variable has 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 need to 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 space occupied by the largest variable; otherwise, it must be a multiple of n.

3. __attribute

Additionally, there is another way: · __attribute((aligned(n))) allows the structure member to align on the n-byte natural boundary. If there are members in the structure whose length is greater than n, they will align according to the maximum member length. · attribute((packed)) cancels the compiler’s optimization alignment during the compilation process and aligns according to the actual number of bytes occupied.

3. Assembly .align

Assembly code typically uses .align to specify the byte alignment.

.align: is used to specify the data alignment method, formatted as follows:

.align [absexpr1, absexpr2]

With a certain alignment method, fill the unused storage area with values. The first value indicates the alignment method, 4, 8, 16, or 32. The second expression value indicates the fill value.

4. Why Align?

The operating system does not access memory byte by byte but accesses it according to word lengths like 2, 4, or 8. Therefore, when the CPU reads data from memory to registers, the length of IO data is usually the word length. For example, in a 32-bit system, the access granularity is 4 bytes, and in a 64-bit system, it is 8 bytes. When the length of the data being accessed is n bytes and the data address is n-byte aligned, the operating system can efficiently locate the data at once without needing multiple reads and handling alignment calculations and other overhead operations. Data structures should be aligned as much as possible on natural boundaries. If unaligned memory is accessed, the CPU needs to perform two memory accesses.

Potential Hazards of Byte Alignment:

The hazards regarding alignment in the code are often implicit. For example, during forced type conversion. For instance:

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

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

The last two lines of code access the unsigned short variable from an odd boundary, which clearly does not comply with the alignment rules. On x86, similar operations would only affect efficiency, but on MIPS or SPARC, it might result in an error because they require byte alignment.

5. Examples

Example 1: Byte Sizes of Basic Data Types in OS

First, check the bitness of the operating system Understanding Byte Alignment in Linux Check the byte sizes of basic data types on a 64-bit operating system:

#include <stdio.h>

int main()
{
    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;
}
Understanding Byte Alignment in Linux

Example 2: Memory Size Occupied by Structures – Default Rules

Consider the following structure’s memory size:

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

Execution result:

sizeof(yikou_t) = 16

The positional relationship of each variable in the content is as follows: Understanding Byte Alignment in Linux

The position of member C is also affected by byte order, which may be at position 8.

The compiler performs memory alignment for us, and the starting address offset of each member variable relative to the structure’s starting address must be a multiple of the number of bytes occupied by that variable type, and the structure’s size must be a multiple of the number of bytes occupied by the type that occupies the maximum space in that structure.

For offsets: The offset of variable type n’s starting address relative to the structure’s starting address must be a multiple of sizeof(type(n)) Structure size: Must be a multiple of the member’s maximum type byte size

char: Offset must be a multiple of sizeof(char) i.e., 1
int: Offset must be a multiple of sizeof(int) i.e., 4
float: Offset must be a multiple of sizeof(float) i.e., 4
double: Offset must be a multiple of sizeof(double) i.e., 8

Example 3: Adjusting Structure Size

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

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

Execution result:

sizeof(yikou_t) = 24

The layout of each variable in memory is as follows: Understanding Byte Alignment in Linux

When the structure has nested composite members, the offset of the composite member relative to the structure’s base address must be a multiple of the integer size of the widest basic type.

Example 4: #pragma pack(4)

#pragma pack(4)

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

Example 5: #pragma pack(8)

#pragma pack(8)

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

Example 6: Assembly Code

For example: The following is a snippet of the U-Boot code containing the entry points for IRQ and FIQ: Understanding Byte Alignment in Linux

6. Summary

For those who are lazy, here is a complete example for you:

#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) /* Specify alignment by 2 bytes */
struct C {
    char b;
    int a;
    short c;
};
#pragma pack () /* Cancel specified alignment, restore default alignment */
 
 
#pragma pack (1) /* Specify alignment by 1 byte */
struct D {
    char b;
    int a;
    short c;
};
#pragma pack ()/* Cancel specified alignment, restore 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);
}

– EOF –

Recommended Reading Click the title to jump

1. 18 tools to analyze programs that occupy network bandwidth on Linux!

2. Do you call this junk load balancing?

3. Data encapsulation for Linux driver programs

Finished readingDid you gain anything from this article? Please share it with more people

We recommend following ‘Linux Enthusiasts’ to enhance your Linux skills

Likes and views are the biggest support ❤️

Leave a Comment