( Powyższe odpowiedzi wyjaśniły powód dość wyraźnie, ale nie wydają się całkowicie jasne co do wielkości wypełnienia, więc dodam odpowiedź zgodnie z tym, czego nauczyłem się z Zaginionej sztuki pakowania struktury , ewoluowała, aby nie ograniczać się do C
, ale również zastosowanie Go
, Rust
. )
Wyrównanie pamięci (dla struct)
Zasady:
- Przed każdym pojedynczym członkiem będzie padding, aby zaczął się od adresu, który można podzielić przez jego rozmiar.
np. w systemie 64-bitowym, int
powinien zaczynać się pod adresem podzielnym przez 4, a long
przez 8 short
przez 2.
char
i char[]
są wyjątkowe, mogą być dowolnym adresem pamięci, więc nie wymagają przed nimi wypełniania.
- Ponieważ
struct
, poza potrzebą wyrównania dla każdego poszczególnego elementu, rozmiar całej struktury zostanie wyrównany do rozmiaru podzielnego przez rozmiar największego pojedynczego elementu, przez wypełnienie na końcu.
np. jeśli największy członek struct jest long
podzielny przez 8, int
następnie przez 4, short
a następnie przez 2.
Zamówienie członka:
- Kolejność członków może wpływać na rzeczywisty rozmiar struktury, więc weź to pod uwagę. np.
stu_c
i stu_d
z poniższego przykładu mają te same elementy, ale w innej kolejności i dają różne rozmiary dla 2 struktur.
Adres w pamięci (dla struct)
Zasady:
- 64-bitowy system
Adres strukturalny zaczyna się od (n * 16)
bajtów. ( W poniższym przykładzie widać, że wszystkie wydrukowane adresy szesnastkowe struktur kończą się na 0
. )
Powód : możliwy największy pojedynczy element struktury ma 16 bajtów ( long double
).
- (Aktualizacja) Jeśli struktura zawiera tylko
char
członka, jej adres może zaczynać się pod dowolnym adresem.
Puste miejsce :
- Pusta przestrzeń między 2 strukturami może zostać wykorzystana przez zmienne niestrukturalne, które mogłyby się zmieścić.
Np . test_struct_address()
Poniżej, zmienna x
znajduje się pomiędzy sąsiednimi strukturami g
i h
.
Bez względu na x
to, czy zostanie zadeklarowany, h
adres się nie zmieni, x
wystarczy ponownie wykorzystać puste miejsce, które g
zmarnowało się.
Podobna sprawa dla y
.
Przykład
( dla systemu 64-bitowego )
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;
}
Wynik wykonania - 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
Wynik wykonania - 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
Zatem początek adresu dla każdej zmiennej to g: d0 x: dc h: e0 y: e8