Structure padding and packing Structure padding and packing c c

Structure padding and packing


Padding aligns structure members to "natural" address boundaries - say, int members would have offsets, which are mod(4) == 0 on 32-bit platform. Padding is on by default. It inserts the following "gaps" into your first structure:

struct mystruct_A {    char a;    char gap_0[3]; /* inserted by compiler: for alignment of b */    int b;    char c;    char gap_1[3]; /* -"-: for alignment of the whole struct in an array */} x;

Packing, on the other hand prevents compiler from doing padding - this has to be explicitly requested - under GCC it's __attribute__((__packed__)), so the following:

struct __attribute__((__packed__)) mystruct_A {    char a;    int b;    char c;};

would produce structure of size 6 on a 32-bit architecture.

A note though - unaligned memory access is slower on architectures that allow it (like x86 and amd64), and is explicitly prohibited on strict alignment architectures like SPARC.


(The above answers explained the reason quite clearly, but seems not totally clear about the size of padding, so, I will add an answer according to what I learned from The Lost Art of Structure Packing, it has evolved to not limit to C, but also applicable to Go, Rust.)


Memory align (for struct)

Rules:

  • Before each individual member, there will be padding so that to make it start at an address that is divisible by its size.
    e.g on 64 bit system,int should start at address divisible by 4, and long by 8, short by 2.
  • char and char[] are special, could be any memory address, so they don't need padding before them.
  • For struct, other than the alignment need for each individual member, the size of whole struct itself will be aligned to a size divisible by size of largest individual member, by padding at end.
    e.g if struct's largest member is long then divisible by 8, int then by 4, short then by 2.

Order of member:

  • The order of member might affect actual size of struct, so take that in mind.e.g the stu_c and stu_d from example below have the same members, but in different order, and result in different size for the 2 structs.

Address in memory (for struct)

Rules:

  • 64 bit system
    Struct address starts from (n * 16) bytes. (You can see in the example below, all printed hex addresses of structs end with 0.)
    Reason: the possible largest individual struct member is 16 bytes (long double).
  • (Update) If a struct only contains a char as member, its address could start at any address.

Empty space:

  • Empty space between 2 structs could be used by non-struct variables that could fit in.
    e.g in test_struct_address() below, the variable x resides between adjacent struct g and h.
    No matter whether x is declared, h's address won't change, x just reused the empty space that g wasted.
    Similar case for y.

Example

(for 64 bit system)

memory_align.c:

/** * Memory align & padding - for struct. * compile: gcc memory_align.c * execute: ./a.out */ #include <stdio.h>// size is 8, 4 + 1, then round to multiple of 4 (int's size),struct stu_a {    int i;    char c;};// size is 16, 8 + 1, then round to multiple of 8 (long's size),struct stu_b {    long l;    char c;};// size is 24, l need padding by 4 before it, then round to multiple of 8 (long's size),struct stu_c {    int i;    long l;    char c;};// size is 16, 8 + 4 + 1, then round to multiple of 8 (long's size),struct stu_d {    long l;    int i;    char c;};// size is 16, 8 + 4 + 1, then round to multiple of 8 (double's size),struct stu_e {    double d;    int i;    char c;};// size is 24, d need align to 8, then round to multiple of 8 (double's size),struct stu_f {    int i;    double d;    char c;};// size is 4,struct stu_g {    int i;};// size is 8,struct stu_h {    long l;};// test - padding within a single struct,int test_struct_padding() {    printf("%s: %ld\n", "stu_a", sizeof(struct stu_a));    printf("%s: %ld\n", "stu_b", sizeof(struct stu_b));    printf("%s: %ld\n", "stu_c", sizeof(struct stu_c));    printf("%s: %ld\n", "stu_d", sizeof(struct stu_d));    printf("%s: %ld\n", "stu_e", sizeof(struct stu_e));    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));    return 0;}// test - address of struct,int test_struct_address() {    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));    struct stu_g g;    struct stu_h h;    struct stu_f f1;    struct stu_f f2;    int x = 1;    long y = 1;    printf("address of %s: %p\n", "g", &g);    printf("address of %s: %p\n", "h", &h);    printf("address of %s: %p\n", "f1", &f1);    printf("address of %s: %p\n", "f2", &f2);    printf("address of %s: %p\n", "x", &x);    printf("address of %s: %p\n", "y", &y);    // g is only 4 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),    printf("space between %s and %s: %ld\n", "g", "h", (long)(&h) - (long)(&g));    // h is only 8 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),    printf("space between %s and %s: %ld\n", "h", "f1", (long)(&f1) - (long)(&h));    // f1 is only 24 bytes itself, but distance to next struct is 32 bytes(on 64 bit system) or 24 bytes(on 32 bit system),    printf("space between %s and %s: %ld\n", "f1", "f2", (long)(&f2) - (long)(&f1));    // x is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between g & h,    printf("space between %s and %s: %ld\n", "x", "f2", (long)(&x) - (long)(&f2));    printf("space between %s and %s: %ld\n", "g", "x", (long)(&x) - (long)(&g));    // y is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between h & f1,    printf("space between %s and %s: %ld\n", "x", "y", (long)(&y) - (long)(&x));    printf("space between %s and %s: %ld\n", "h", "y", (long)(&y) - (long)(&h));    return 0;}int main(int argc, char * argv[]) {    test_struct_padding();    // test_struct_address();    return 0;}

Execution result - test_struct_padding():

stu_a: 8stu_b: 16stu_c: 24stu_d: 16stu_e: 16stu_f: 24stu_g: 4stu_h: 8

Execution result - test_struct_address():

stu_g: 4stu_h: 8stu_f: 24address of g: 0x7fffd63a95d0  // struct variable - address dividable by 16,address of h: 0x7fffd63a95e0  // struct variable - address dividable by 16,address of f1: 0x7fffd63a95f0 // struct variable - address dividable by 16,address of f2: 0x7fffd63a9610 // struct variable - address dividable by 16,address of x: 0x7fffd63a95dc  // non-struct variable - resides within the empty space between struct variable g & h.address of y: 0x7fffd63a95e8  // non-struct variable - resides within the empty space between struct variable h & f1.space between g and h: 16space between h and f1: 16space between f1 and f2: 32space between x and f2: -52space between g and x: 12space between x and y: 12space between h and y: 8

Thus address start for each variable is g:d0 x:dc h:e0 y:e8

enter image description here


I know this question is old and most answers here explains padding really well, but while trying to understand it myself I figured having a "visual" image of what is happening helped.

The processor reads the memory in "chunks" of a definite size (word). Say the processor word is 8 bytes long. It will look at the memory as a big row of 8 bytes building blocks. Every time it needs to get some information from the memory, it will reach one of those blocks and get it.

Variables Alignment

As seem in the image above, doesn't matter where a Char (1 byte long) is, since it will be inside one of those blocks, requiring the CPU to process only 1 word.

When we deal with data larger than one byte, like a 4 byte int or a 8 byte double, the way they are aligned in the memory makes a difference on how many words will have to be processed by the CPU. If 4-byte chunks are aligned in a way they always fit the inside of a block (memory address being a multiple of 4) only one word will have to be processed. Otherwise a chunk of 4-bytes could have part of itself on one block and part on another, requiring the processor to process 2 words to read this data.

The same applies to a 8-byte double, except now it must be in a memory address multiple of 8 to guarantee it will always be inside a block.

This considers a 8-byte word processor, but the concept applies to other sizes of words.

The padding works by filling the gaps between those data to make sure they are aligned with those blocks, thus improving the performance while reading the memory.

However, as stated on others answers, sometimes the space matters more then performance itself. Maybe you are processing lots of data on a computer that doesn't have much RAM (swap space could be used but it is MUCH slower). You could arrange the variables in the program until the least padding is done (as it was greatly exemplified in some other answers) but if that's not enough you could explicitly disable padding, which is what packing is.