Understanding Byte Alignment in Linux

Recently encountered a problem where ThreadX running on ARM crashed while communicating with DSP using message queues (the final implementation principle is interrupt + shared memory). After investigation, it was found that the structure used for message passing did not consider the issue of byte alignment.

Here’s a brief overview of byte alignment issues in C language to share 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 a computer uses to process transactions at once

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 amounts of data at once; 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 bits refer to the word length.

The so-called word length is sometimes referred to as 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 using words as the unit, there are also double words (two words) and quad words (four words).

2. Alignment Rules

For standard data types, the address must be a multiple of its length; for non-standard data types, alignment is based on the following principles: Arrays: aligned according to basic data types, once the first is aligned, the others are naturally aligned. Unions: align according to the largest data type it contains. Structures: each data type in the structure must be aligned.

3. How to Limit 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)

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

#pragma pack(n) is used to set variables to align in n byte increments. n byte alignment means that the starting address offset of the variable has two situations:

  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, then the offset must be a multiple of n, without needing 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 the following method: · __attribute((aligned (n))) allows the structure members to be aligned on n-byte natural boundaries. If there are members in the structure whose lengths exceed n, they will be aligned according to the maximum member length. · attribute ((packed)) cancels the optimization alignment of the structure during compilation, aligning according to the actual number of bytes occupied.

4. Assembly .align

Assembly code usually 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 unused storage areas with values. The first value indicates the alignment method, 4, 8, 16, or 32. The second expression indicates the padding value.

4. Why Align?

The operating system does not access memory byte by byte but accesses it in word lengths such as 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, the access granularity of a 32-bit system is 4 bytes, while for a 64-bit system, it is 8 bytes. When the length of the accessed data is n bytes and the data address is n bytes aligned, the operating system can efficiently locate the data at once, without needing to read multiple times or perform additional operations such as alignment calculations. Data structures should be aligned as much as possible on natural boundaries. If accessing unaligned memory, the CPU needs to perform two memory accesses.

Potential issues with byte alignment:

Many alignment issues in code are implicit. For example, during type casting. 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 an unsigned short variable from an odd boundary, which clearly does not conform to alignment regulations. On x86, similar operations only affect efficiency, but on MIPS or Sparc, it may cause an error, as they require byte alignment.

5. Examples

Example 1: Byte Size of Basic Data Types in OS

First, check the bitness of the operating systemUnderstanding Byte Alignment in LinuxCheck the byte size of basic data types in 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 Structure — Default Rules

Consider the following structure’s occupied bits

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

Execution result

sizeof(yikou_t) = 16

The relationship of each variable’s position 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 must be 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 that occupies the maximum space in the structure.

For offset: The starting address offset of variable type n 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

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 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 composite members relative to the structure’s base address is a multiple of the 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

Example: The following is a snippet of uboot code for the entry points of exception vectors irq and fiq:Understanding Byte Alignment in Linux

6. Summary

For those who are lazy, here’s 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) /* Align by 2 bytes */
struct C {
    char b;
    int a;
    short c;
};
#pragma pack () /* Cancel specified alignment, restore default alignment */
 
 
#pragma pack (1) /* Align 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);
}
All articles from this public account have been organized into a directory, please reply with “m” in the public account to get it!

Recommended Reading:

Hacker Shell Skills: Covering Up Operation Traces on Linux Servers

After browsing these Java communities, I floated~

How to Effectively Analyze Linux Server Performance Issues in 60 Seconds

5T Technical Resources Giveaway! Including but not limited to: C/C++, Linux, Python, Java, PHP, Artificial Intelligence, Microcontrollers, Raspberry Pi, etc. Reply “1024” in the public account to get it for free!!

Understanding Byte Alignment in Linux

Leave a Comment

×