Bài 53.126 phút đọc
Quy tắc bố cục struct
Sau bài này bạn sẽ làm được
- Phát biểu năm quy tắc bố cục
- Tính sizeof và offsetof của một struct bằng tay rồi kiểm chứng
- Giải thích vì sao có đệm ở cuối struct
- Dùng -Wpadded để thấy mọi chỗ chèn đệm
struct { char a; int b; char c; } có ba trường tổng cộng sáu byte, và sizeof cho ra mười hai. Chuẩn C bảo đảm đúng năm điều về bố cục, và năm điều đó đủ để bạn tính ra con số này bằng tay.
#Năm quy tắc
| Quy tắc | Hệ quả | |
|---|---|---|
| 1 | Thành viên xếp theo đúng thứ tự khai báo | Trường khai báo trước có độ dời nhỏ hơn |
| 2 | Mỗi thành viên nằm ở độ dời chia hết cho _Alignof của nó | Có thể phải chèn đệm GIỮA các thành viên |
| 3 | Căn chỉnh của struct bằng căn chỉnh lớn nhất trong các thành viên | struct chứa double thì căn chỉnh 8 |
| 4 | sizeof(struct) chia hết cho căn chỉnh của nó | Có thể phải chèn đệm Ở CUỐI |
| 5 | Không có đệm TRƯỚC thành viên đầu tiên | offsetof của trường đầu luôn bằng 0 |
#Tính bằng tay
struct Data { char a; int b; char c; }
Buoc 1: a la char, _Alignof(char) = 1
do doi 0 chia het cho 1 -> a o do doi 0, chiem 1 byte
vi tri hien tai: 1
Buoc 2: b la int, _Alignof(int) = 4
do doi 1 KHONG chia het cho 4
-> chen 3 byte dem, b o do doi 4, chiem 4 byte
vi tri hien tai: 8
Buoc 3: c la char, _Alignof(char) = 1
do doi 8 chia het cho 1 -> c o do doi 8, chiem 1 byte
vi tri hien tai: 9
Buoc 4: can chinh cua struct = max(1, 4, 1) = 4
9 KHONG chia het cho 4
-> chen 3 byte dem o cuoi
Ket qua: sizeof = 12, _Alignof = 4
offsetof(a) = 0, offsetof(b) = 4, offsetof(c) = 8Bản đồ byte
byte: 0 1 2 3 4 5 6 7 8 9 10 11
+----+----------+------------+----+--------+
| a | DEM | b | c | DEM |
+----+----------+------------+----+--------+
1 byte 3 byte 4 byte 1 byte 3 byte
Sau byte dem: 6 byte du lieu, 6 byte dem. Mot nua bi lang phi.
Bai 53.2 se cho thay cach dua no xuong 8.#Kiểm chứng bằng offsetof
bocuc.c
#include <stdio.h>
#include <stddef.h>
#include <stdalign.h>
struct Data { char a; int b; char c; };
int main(void) {
printf("sizeof = %d\n", (int)sizeof(struct Data));
printf("alignof = %d\n", (int)_Alignof(struct Data));
printf("offset a = %d\n", (int)offsetof(struct Data, a));
printf("offset b = %d\n", (int)offsetof(struct Data, b));
printf("offset c = %d\n", (int)offsetof(struct Data, c));
return 0;
}terminal
gcc -std=c11 -O2 -o bocuc.exe bocuc.c && ./bocuc.exe
sizeof = 12 alignof = 4 offset a = 0 offset b = 4 offset c = 8
Khớp chính xác với phép tính bằng tay: 12, 4, và ba độ dời 0, 4, 8.
#Byte đệm chứa gì
Bốn hệ quả của việc đệm không xác định
/* 1. memcmp khong dung duoc, nhu tren */
/* 2. Ham bam tren struct khong on dinh */
uint64_t bam(const void *p, size_t n); /* Bai 52.3 */
bam(&x, sizeof x); /* KHONG on dinh: dem la rac */
/* Chua: bam tung truong */
uint64_t bam_data(const struct Data *d) {
uint64_t h = 14695981039346656037ULL;
h = (h ^ (uint64_t)d->a) * 1099511628211ULL;
h = (h ^ (uint64_t)d->b) * 1099511628211ULL;
h = (h ^ (uint64_t)d->c) * 1099511628211ULL;
return h;
}
/* 3. Ghi struct ra tep lam lo du lieu cu trong phan dem */
fwrite(&x, sizeof x, 1, f);
/* Sau byte dem co the chua manh du lieu cua bien truoc do
tren ngan xep. Do la mot lo hong ro ri thong tin THAT,
da tung xay ra trong nhan Linux nhieu lan. */
/* 4. So sanh struct bang == khong ton tai trong C */
if (x == y) { } /* error: invalid operands to binary == */
/* C khong dinh nghia toan tu so sanh cho struct, chinh vi van de dem. */#Xem mọi chỗ chèn đệm
terminal
gcc -std=c11 -Wpadded -c bocuc.c -o /dev/null
bocuc.c:5:8: warning: padding struct size to alignment boundary [-Wpadded]
struct Data { char a; int b; char c; };
^~~~
bocuc.c:6:8: warning: padding struct size to alignment boundary [-Wpadded]
struct Kem { char a; double b; char c; };
^~~
bocuc.c:7:8: warning: padding struct size to alignment boundary [-Wpadded]
struct Tot { double b; char a; char c; };
^~~# pahole: doc thong tin go loi va in bo cuc kem cho lang phi
$ gcc -g -c bocuc.c
$ pahole bocuc.o
struct Data {
char a; /* 0 1 */
/* XXX 3 bytes hole, try to pack */
int b; /* 4 4 */
char c; /* 8 1 */
/* size: 12, cachelines: 1, members: 3 */
/* sum members: 6, holes: 1, sum holes: 3 */
/* padding: 3 */
/* last cacheline: 12 bytes */
};
# Ba dong cuoi la thu ban can:
# sum members: 6 du lieu that
# sum holes: 3 phi o giua
# padding: 3 phi o cuoi
#
# Tong phi 6 tren 12 byte, tuc 50 phan tram.
$ pahole --sizes bocuc.o # liet ke moi struct theo kich thuoc
$ pahole --packable bocuc.o # chi nhung struct co the nen lai duoc
$ pahole -R bocuc.o # de xuat thu tu truong toi uuTự làm thử
- Tính bằng tay
sizeofcủa năm struct rồi kiểm chứng bằng chương trình. - Vẽ bản đồ byte cho một struct có bốn trường khác kiểu.
- Chứng minh
memcmptrên struct có đệm không đáng tin. - Chạy
-Wpaddedtrên một dự án và đếm số struct bị cảnh báo. - Dùng
ptype/ocủa GDB trên ba struct của bạn. - Cài
paholetrong WSL và chạy nó trên một tệp đối tượng.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Năm quy tắc: thứ tự khai báo, độ dời chia hết cho căn chỉnh, căn chỉnh struct là lớn nhất,
sizeofchia hết cho căn chỉnh, không đệm trước trường đầu. struct { char; int; char; }cho ra 12 byte với 6 byte dữ liệu, tức lãng phí một nửa.- Đệm ở cuối tồn tại để mọi phần tử trong mảng đều được căn chỉnh đúng.
- Byte đệm không có giá trị xác định, nên
memcmpvà băm trên struct đều không đáng tin. - Chuẩn bảo đảm con trỏ tới struct trỏ tới thành viên đầu tiên, và đó là cơ sở cho kỹ thuật kế thừa thủ công.