( Các câu trả lời trên đã giải thích lý do khá rõ ràng, nhưng dường như không hoàn toàn rõ ràng về kích thước của phần đệm, vì vậy, tôi sẽ thêm một câu trả lời theo những gì tôi học được từ The Lost Art of Architecture Packaging , nó đã phát triển để không giới hạn C
, nhưng cũng áp dụng cho Go
, Rust
. )
Căn chỉnh bộ nhớ (cho struct)
Quy tắc:
- Trước mỗi thành viên riêng lẻ, sẽ có phần đệm để làm cho nó bắt đầu tại một địa chỉ chia hết cho kích thước của nó.
ví dụ: trên hệ thống 64 bit, int
nên bắt đầu tại địa chỉ chia hết cho 4 và long
8, short
cho 2.
char
và char[]
đặc biệt, có thể là bất kỳ địa chỉ bộ nhớ nào, vì vậy chúng không cần đệm trước chúng.
- Đối với
struct
, ngoài nhu cầu căn chỉnh cho từng thành viên riêng lẻ, kích thước của toàn bộ cấu trúc sẽ được căn chỉnh theo kích thước chia hết cho kích thước của thành viên lớn nhất, bằng cách đệm ở cuối.
ví dụ: nếu thành viên lớn nhất của struct long
chia hết cho 8, int
rồi 4, short
rồi 2.
Thứ tự của thành viên:
- Thứ tự của thành viên có thể ảnh hưởng đến kích thước thực tế của struct, vì vậy hãy ghi nhớ điều đó. ví dụ
stu_c
và stu_d
ví dụ dưới đây có cùng các thành viên, nhưng theo thứ tự khác nhau và dẫn đến kích thước khác nhau cho 2 cấu trúc.
Địa chỉ trong bộ nhớ (cho struct)
Quy tắc:
- Hệ thống 64 bit
Địa chỉ cấu trúc bắt đầu từ (n * 16)
byte. ( Bạn có thể thấy trong ví dụ bên dưới, tất cả các địa chỉ hex được in của các cấu trúc kết thúc bằng 0
. )
Lý do : thành viên cấu trúc riêng lẻ lớn nhất có thể là 16 byte ( long double
).
- (Cập nhật) Nếu một cấu trúc chỉ chứa một
char
thành viên, địa chỉ của nó có thể bắt đầu tại bất kỳ địa chỉ nào.
Không gian trống :
- Không gian trống giữa 2 cấu trúc có thể được sử dụng bởi các biến không có cấu trúc có thể phù hợp.
Ví dụ như test_struct_address()
bên dưới, biến x
nằm giữa struct liền kề g
và h
.
Cho dù x
được khai báo, h
địa chỉ sẽ không thay đổi, x
chỉ sử dụng lại không gian trống g
bị lãng phí.
Trường hợp tương tự cho y
.
Thí dụ
( đối với hệ thống 64 bit )
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;
}
Kết quả thực hiện - 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
Kết quả thực hiện - 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
Do đó, địa chỉ bắt đầu cho mỗi biến là g: d0 x: dc h: e0 y: e8