Hello! Let's continue our journey into the low-level details of C.
In our last session, we focused on memory management, specifically the dangers of memory fragmentation and why safety-critical standards like MISRA C forbid dynamic allocation. We concluded that for predictable and reliable systems, understanding memory is paramount.
Today, we shift our focus from memory management to memory layout. We will examine how a compiler organizes data within a struct. This isn't arbitrary; it's governed by rules that are critical for performance and for interfacing with hardware. For your work in ADAS and gateway development, where you map software constructs to hardware registers or network packet formats, a deep understanding of this topic is non-negotiable.
Our learning outcome for this lesson is to: Explain how a compiler inserts padding bytes into structures for data alignment.
1. The "Why": Data Alignment and Performance
Before we can talk about padding, we must first understand alignment. At a fundamental level, modern processors don't read memory byte by byte. They are optimized to fetch data in larger chunks, typically the size of their native word (e.g., 4 bytes on a 32-bit CPU, 8 bytes on a 64-bit CPU).
An alignment requirement is a rule that states an object of a certain type must be stored at a memory address that is a multiple of its size. For example:
- A
char(1 byte) can be stored at any address. - A
short(2 bytes) should be stored at an even address (a multiple of 2). - An
int(4 bytes) should be stored at an address that is a multiple of 4. - A
doubleorlong long(8 bytes) should be stored at an address that is a multiple of 8.
Why? If a 4-byte int starts at an address that isn't a multiple of 4 (e.g., address 0x1001), it might span two processor-word boundaries. To read this "misaligned" integer, the CPU might need to perform two separate memory fetches and then stitch the results together. This is significantly slower than a single fetch for an "aligned" integer. On some architectures, particularly older or stricter embedded processors, a misaligned access doesn't just slow you down—it can cause a hardware exception or fault.
The following article provides an excellent background on these fundamental alignment requirements.
The Lost Art of C Structure Packing
Let's read a section from 'The Lost Art of C Structure Packing' by Eric S. Raymond. It clearly explains the motivation behind alignment on modern processors like x86 and ARM, which are common in the automotive world.
Please read the section titled '3. Alignment requirements'. Focus on the alignment rules for basic C types and the performance reasoning provided.
2. The "How": Introducing Padding
Now that we know the why, let's look at the how. To enforce these alignment rules, the compiler often has no choice but to insert unused bytes into your data structures. This unused space is called padding.
Imagine the compiler laying out variables or struct members in memory one after another. If placing a member immediately after the previous one would violate its alignment requirement, the compiler inserts padding to shift it to the next valid address.
This image provides a perfect visual of the concept. On the left is a hypothetical, tightly packed struct. On the right is what the compiler actually produces, inserting padding to align the long x member.

To see this with a simple sequence of variables, the next section of the same article is very helpful.
The Lost Art of C Structure Packing
Let's continue with the same article. The next section demonstrates how padding is introduced even for simple, sequential variable declarations, which serves as a great stepping stone to understanding structs.
Now, please read the section '4. Padding'. Observe how the compiler introduces 'slop' (padding) to align variables like int x and short x when they follow a char.
3. Padding and Alignment Rules within a struct
When these concepts are applied to a struct, there are three fundamental rules you must understand to predict its memory layout and size.
Rule 1: Member Alignment
Each member of the struct is placed at an offset from the beginning of the struct. This offset must be a multiple of that member's alignment requirement. Padding bytes are inserted between members to satisfy this rule.
Rule 2: Struct Alignment
The alignment of the entire struct is determined by the alignment of its widest member. For example, if a struct contains a char, an int, and a short, its widest member is the int, which has a 4-byte alignment. Therefore, the entire struct will have a 4-byte alignment. This means the starting address of any instance of this struct must be a multiple of 4.
Rule 3: Trailing Padding
The total size of the struct must be a multiple of its alignment (from Rule 2). The compiler adds padding at the end of the struct if necessary to meet this requirement. This is crucial for arrays of structs, as it ensures that every element in the array is correctly aligned, and thus every member within every element is also correctly aligned.
Let's walk through an example. Consider this struct on a typical 64-bit system where int is 4 bytes and long is 8 bytes.
struct example {
int a; // 4 bytes, 4-byte alignment
char b; // 1 byte, 1-byte alignment
long c; // 8 bytes, 8-byte alignment
char d; // 1 byte, 1-byte alignment
};
Let's calculate its layout and size:
- Struct Alignment: The widest member is
long c(8-byte alignment). So,sizeof(struct example)must be a multiple of 8. int a: Placed at offset 0. It ends at offset 3.char b: Placed at offset 4. It ends at offset 4.long c: Needs 8-byte alignment. The next available offset is 5, which is not a multiple of 8. The next valid offset is 8. The compiler inserts 3 bytes of padding afterb.cis placed at offset 8. It ends at offset 15.char d: Placed at offset 16. It ends at offset 16.- Trailing Padding: The members end at offset 16. The total size so far is 17 bytes. The struct's alignment is 8. The next multiple of 8 after 17 is 24. The compiler inserts 7 bytes of trailing padding.
The final size is 24 bytes.
This image shows a similar real-world example and its resulting memory map.

The following resources provide a comprehensive explanation of these rules, reinforcing what we've just discussed.
C Structs and Memory Alignment Basics
This article from Alexander Obregon's Substack, 'C Structs and Memory Alignment Basics', provides a clear, rule-based explanation of the layout process. It's a great way to formalize your understanding.
Please read the section 'Field Order With Padding Rules'. It introduces the offsetof macro and explicitly describes the process of placing fields and adding internal and trailing padding.
The Lost Art of C Structure Packing
Finally, let's return to 'The Lost Art of C Structure Packing' to see these rules applied to several struct examples, including the concept of a 'stride address' which is a useful way to think about trailing padding.
Read the section '5. Structure alignment and padding'. Pay close attention to the examples struct foo3 and struct foo4, as they clearly illustrate how trailing padding is calculated based on the struct's overall alignment.
Conclusion
Understanding data alignment and padding is not just an academic exercise; it has direct, practical implications in your work. When you define a struct to overlay on a hardware register block from a microcontroller datasheet, or to represent a network packet defined by a standard like SOME/IP, you must know exactly where each byte will be. A miscalculation due to padding can lead to writing data to the wrong hardware bit or sending a malformed network message.
Key Takeaways:
- Alignment: Modern CPUs require data to be stored at memory addresses that are multiples of their size for efficient access. Misaligned access can cause performance penalties or hardware faults.
- Padding: Compilers insert unused bytes (padding) into structs to ensure every member satisfies its alignment requirement.
- Struct Padding Rules:
- Padding is inserted between members to align them correctly.
- The entire struct has an alignment equal to that of its widest member.
- Padding is added at the end of the struct (trailing padding) to make its total size a multiple of its alignment. This ensures correctness for arrays of structs.
In our next lesson, we will put this knowledge to practical use. We will focus on calculating the size of a struct and then explore how reordering struct members can change its total size, a key optimization technique for reducing memory footprint.