C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
66: Structure Padding and Alignment
You've probably spent the last few lessons getting comfortable with structs, and at this point, you likely view them as simple containers. If you put a 1-byte char and a 4-byte int into a struct, it seems only logical that the struct would take up 5 bytes of memory. It's basic addition, right?
The "Simple Addition" Fallacy
I see this mistake constantly. Learners assume that sizeof(struct) is always exactly the sum of its members. Let's prove why that's not the case with a quick example. Consider this snippet:
struct DeviceStatus {
char is_active; // 1 byte
int error_code; // 4 bytes
};
printf("Size: %zu\n", sizeof(struct DeviceStatus));
If you run this on a standard 64-bit system, you aren't going to see "5". You're going to see "8". You might think your compiler is broken or that there's some weird overhead to creating a struct, but the reality is more intentional. The compiler has silently inserted 3 bytes of "padding" between is_active and error_code.
The Hidden Tax: Alignment and Padding
Why the waste? It comes down to how your CPU actually talks to your RAM. Most modern processors don't like reading data from arbitrary memory addresses. They are optimized to read data from addresses that are multiples of the data's own size. This is called alignment.
An int (usually 4 bytes) is most efficiently read when its address is a multiple of 4. If the compiler placed the int immediately after the 1-byte char, the int would start at offset 1—which is not a multiple of 4. The CPU would then have to do two memory reads and some bit-shifting to "stitch" the integer together, which kills performance. To avoid this, the compiler inserts padding bytes to ensure the int starts at offset 4.
I'll give you a tip: the struct's overall alignment is determined by its largest member. If you have a double (8 bytes) in your struct, the entire struct will be padded so that its total size is a multiple of 8. This ensures that if you create an array of these structs, every single element in that array remains properly aligned.
Rearranging for Efficiency
Now, here is the part where you can actually control this. While you can't easily tell the compiler "don't pad this" (though #pragma pack exists, I generally advise against it unless you're writing a network driver), you can minimize the waste by ordering your members from largest to smallest.
Look at the difference here:
// Wasteful layout: 12 bytes
struct BadLayout {
char a; // 1 byte + 3 padding
int b; // 4 bytes
char c; // 1 byte + 3 padding
};
// Optimized layout: 8 bytes
struct GoodLayout {
int b; // 4 bytes
char a; // 1 byte
char c; // 1 byte + 2 padding
};
By grouping the smaller types together at the end, we reduced the padding from 6 bytes down to 2. In a small program, this doesn't matter. But if you're allocating an array of a million BadLayout structs, you're wasting 4 megabytes of RAM for absolutely no reason. That's the kind of detail that separates a hobbyist from a professional engineer.
📋 Practical Task
The Struct Packing Audit
Your goal is to write a program that demonstrates your understanding of alignment by creating a "memory-waste" report.
- Define a struct called
NetworkPacketthat contains the following members in this exact order:char version,int packet_id,char flags, anddouble timestamp. - Define a second struct called
OptimizedPacketcontaining the exact same members, but reordered to minimize padding. - In your
mainfunction, usesizeof()to print the size of both structs. - Calculate and print the number of bytes wasted in the
NetworkPacketcompared to theOptimizedPacket. - As an extra challenge, use the
&(address-of) operator to print the memory addresses of the members within theNetworkPacketstruct to prove that theintanddoubleare starting on aligned boundaries.
There are no comments for now.