Structure padding and packing

前端 未结 9 1245
太阳男子
太阳男子 2020-11-21 05:04

Consider:

struct mystruct_A
{
   char a;
   int b;
   char c;
} x;

struct mystruct_B
{
   int b;
   char a;
} y;

The sizes of the structur

相关标签:
9条回答
  • 2020-11-21 05:32

    (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: 8
    stu_b: 16
    stu_c: 24
    stu_d: 16
    stu_e: 16
    stu_f: 24
    stu_g: 4
    stu_h: 8
    

    Execution result - test_struct_address():

    stu_g: 4
    stu_h: 8
    stu_f: 24
    address 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: 16
    space between h and f1: 16
    space between f1 and f2: 32
    space between x and f2: -52
    space between g and x: 12
    space between x and y: 12
    space between h and y: 8
    

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

    0 讨论(0)
  • 2020-11-21 05:37

    Rules for padding:

    1. Every member of the struct should be at an address divisible by its size. Padding is inserted between elements or at the end of the struct to make sure this rule is met. This is done for easier and more efficient Bus access by the hardware.
    2. Padding at the end of the struct is decided based on the size of the largest member of the struct.

    Why Rule 2: Consider the following struct,

    If we were to create an array(of 2 structs) of this struct, No padding will be required at the end:

    Therefore, size of struct = 8 bytes

    Assume we were to create another struct as below:

    If we were to create an array of this struct, there are 2 possibilities, of the number of bytes of padding required at the end.

    A. If we add 3 bytes at the end and align it for int and not Long:

    B. If we add 7 bytes at the end and align it for Long:

    The start address of the second array is a multiple of 8(i.e 24). The size of the struct = 24 bytes

    Therefore, by aligning the start address of the next array of the struct to a multiple of the largest member(i.e if we were to create an array of this struct, the first address of the second array must start at an address which is a multiple of the largest member of the struct. Here it is, 24(3 * 8)), we can calculate the number of padding bytes required at the end.

    0 讨论(0)
  • 2020-11-21 05:37

    Data structure alignment is the way data is arranged and accessed in computer memory. It consists of two separate but related issues: data alignment and data structure padding. When a modern computer reads from or writes to a memory address, it will do this in word sized chunks (e.g. 4 byte chunks on a 32-bit system) or larger. Data alignment means putting the data at a memory address equal to some multiple of the word size, which increases the system’s performance due to the way the CPU handles memory. To align the data, it may be necessary to insert some meaningless bytes between the end of the last data structure and the start of the next, which is data structure padding.

    1. In order to align the data in memory, one or more empty bytes (addresses) are inserted (or left empty) between memory addresses which are allocated for other structure members while memory allocation. This concept is called structure padding.
    2. Architecture of a computer processor is such a way that it can read 1 word (4 byte in 32 bit processor) from memory at a time.
    3. To make use of this advantage of processor, data are always aligned as 4 bytes package which leads to insert empty addresses between other member’s address.
    4. Because of this structure padding concept in C, size of the structure is always not same as what we think.
    0 讨论(0)
提交回复
热议问题