Bài 53.326 phút đọc
offsetof, container_of và kiểm tra bố cục
Sau bài này bạn sẽ làm được
- Dùng offsetof đúng cách
- Cài container_of và giải thích phép trừ trong đó
- Viết _Static_assert chốt kích thước và độ dời
- Biết vì sao không nên tự tính độ dời bằng tay
offsetof cho bạn khoảng cách từ đầu struct tới một trường. container_of đi ngược lại: từ địa chỉ một trường, tính ra địa chỉ struct chứa nó. Cặp này là nền của mọi cấu trúc dữ liệu xâm nhập.
#offsetof
#include <stddef.h>
size_t offsetof(type, member); /* la mot MACRO, khong phai ham */
struct Data { char a; int b; char c; };
offsetof(struct Data, a) /* 0 */
offsetof(struct Data, b) /* 4 */
offsetof(struct Data, c) /* 8 */
/* Ket qua la mot BIEU THUC HANG luc dich, nen dung duoc trong
_Static_assert, trong kich thuoc mang, va trong nhan case. */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
#container_of
container_of
Từ một con trỏ tới trường
m của một struct kiểu T, tính ra con trỏ tới chính struct đó. Phép tính là trừ đi độ dời của trường.Bản đơn giản nhất
#include <stddef.h>
#define VE_CHU(ptr, T, m) \
((T *)((char *)(ptr) - offsetof(T, m)))
/* Vi du: */
typedef struct Nut { struct Nut *truoc, *sau; } Nut;
typedef struct {
int ma; /* do doi 0, 4 byte */
char ten[16]; /* do doi 4, 16 byte */
/* DEM 20, 4 byte, vi Nut can can chinh 8 */
Nut moc; /* do doi 24, 16 byte */
} SinhVien; /* sizeof 40 */
Nut *n = ...; /* con tro toi moc */
SinhVien *sv = VE_CHU(n, SinhVien, moc); /* con tro toi ca sinh vien */Vì sao phép trừ đó đúng
/* Bo nho:
dia chi cua sv dia chi cua sv->moc
| |
v v
+----+----------------+------+------------------------+
| ma | ten | DEM | moc |
+----+----------------+------+------------------------+
0 4 20 24 40
offsetof(SinhVien, moc) = 24
Nen: dia chi cua sv = dia chi cua sv->moc - 24
Chu y con so 24 chu khong phai 20: "ten" ket thuc o byte 20,
nhung Nut chua hai con tro nen can can chinh 8, va 20 khong
chia het cho 8. Trinh bien dich chen 4 byte dem. Bai 53.1.
Ep sang char * truoc khi tru la BAT BUOC:
- so hoc con tro tren SinhVien * se tru theo don vi sizeof(SinhVien)
- so hoc tren char * tru theo don vi 1 byte, la cai ban muon
- va so hoc tren void * la mo rong cua GNU, khong phai chuan */terminal
# Hợp ngữ: container_of biến thành đúng một lệnh
gcc -O2 -S -masm=intel -o - vechu.c
ve_chu:
lea rax, -24[rcx]
ret#Làm cho nó an toàn về kiểu
Bản không kiểm tra
#define VE_CHU(ptr, T, m) \
((T *)((char *)(ptr) - offsetof(T, m)))
/* Khong ai kiem tra rang "ptr" that su tro toi truong "m".
Neu ban viet nham: */
Nut *n = ...;
SinhVien *sv = VE_CHU(n, SinhVien, ten); /* SAI truong, dich duoc! */
/* Ket qua la mot con tro tro sai cho, va moi truy cap sau do
deu la rac. Khong canh bao nao. */Bản kiểm tra kiểu
/* Ban cua nhan Linux, dung typeof de kiem tra kieu: */
#define VE_CHU(ptr, T, m) __extension__({ \
const __typeof__(((T *)0)->m) *_mptr = (ptr); \
(T *)((char *)_mptr - offsetof(T, m)); \
})
Nut *n = ...;
SinhVien *sv = VE_CHU(n, SinhVien, ten);
/* warning: initialization of 'const char (*)[16]' from incompatible
pointer type 'Nut *' */
/* Dong dau gan "ptr" vao mot con tro co KIEU CUA TRUONG.
Neu kieu khong khop thi trinh bien dich canh bao.
Chi phi: dung __typeof__ va bieu thuc khoi, ca hai la mo rong GNU.
Nen bao no trong #if defined(__GNUC__). */Bản đầy đủ, chạy được ở mọi nơi
#include <stddef.h>
#if defined(__GNUC__) || defined(__clang__)
#define VE_CHU(ptr, T, m) __extension__({ \
const __typeof__(((T *)0)->m) *_mp = (ptr); \
(T *)((char *)_mp - offsetof(T, m)); \
})
#else
#define VE_CHU(ptr, T, m) \
((T *)((char *)(ptr) - offsetof(T, m)))
#endif
/* Tren GCC va Clang: co kiem tra kieu.
Tren trinh bien dich khac: khong co, nhung van chay dung.
Do la mau tot cho moi macro dung mo rong: ban day du cho
trinh bien dich chinh, ban toi thieu cho phan con lai. */#Chốt bố cục lúc dịch
Bốn loại khẳng định đáng đặt
#include <stddef.h>
#include <assert.h>
/* 1. Kich thuoc, khi struct la mot phan ABI hoac dinh dang tep */
_Static_assert(sizeof(BanGhi) == 112,
"dinh dang tep v1: kich thuoc ban ghi phai la 112");
/* 2. Do doi tung truong */
_Static_assert(offsetof(BanGhi, ma) == 0, "bo cuc: ma");
_Static_assert(offsetof(BanGhi, ten) == 4, "bo cuc: ten");
_Static_assert(offsetof(BanGhi, diem) == 104, "bo cuc: diem");
/* 3. Truong dau tien, cho ke thua thu cong. Bai 29.5 */
_Static_assert(offsetof(HinhTron, co_so) == 0,
"co_so phai la truong dau tien");
/* 4. Khong co lo trong nao */
_Static_assert(sizeof(SinhVien) ==
sizeof(double) + sizeof(long) + 32 + 8 + sizeof(int) + 2,
"SinhVien co lo trong, xem lai thu tu truong");terminal
gcc -std=c11 -c gd.c
gd.c:10:1: error: static assertion failed: "ma nay gia dinh int it nhat 3200 bit" _Static_assert(sizeof(int) >= 400, ^~~~~~~~~~~~~~
Một macro sinh khẳng định cho mọi trường
/* Ket hop voi X Macro cua Bai 54.3: */
#define CAC_TRUONG(X) \
X(ma, 0, 4) \
X(ten, 4, 64) \
X(diem, 104, 8)
typedef struct {
uint32_t ma;
char ten[64];
uint32_t dem; /* 4 byte dem, co y */
double diem;
} BanGhi;
/* Sinh khang dinh cho tung truong: */
#define X(ten_truong, do_doi, co) \
_Static_assert(offsetof(BanGhi, ten_truong) == (do_doi), \
"bo cuc: " #ten_truong " sai do doi"); \
_Static_assert(sizeof(((BanGhi *)0)->ten_truong) == (co), \
"bo cuc: " #ten_truong " sai kich thuoc");
CAC_TRUONG(X)
#undef X
/* Them mot truong vao danh sach la tu dong co hai khang dinh moi.
Bai 54.3 se noi ky ve ky thuat nay. */#Ba ứng dụng thật
Một: danh sách xâm nhập
typedef struct Nut { struct Nut *truoc, *sau; } Nut;
typedef struct {
int ma;
Nut moc;
} SinhVien;
#define DUYET(bien, dau) \
for (Nut *bien = (dau)->sau; bien != (dau); bien = bien->sau)
DUYET(n, &dau) {
SinhVien *sv = VE_CHU(n, SinhVien, moc);
printf("%d\n", sv->ma);
}
/* Uu diem so voi danh sach thong thuong:
- khong cap phat rieng cho nut
- mot doi tuong nam duoc trong NHIEU danh sach cung luc,
moi danh sach mot truong moc
- ma danh sach hoan toan doc lap voi kieu du lieu
Bai 30.3 da noi ky. */Hai: hàm gọi lại lấy lại ngữ cảnh
typedef struct {
void (*khi_xong)(struct YeuCau *yc);
} YeuCau;
typedef struct {
YeuCau yc; /* truong dau tien */
char duong_dan[256];
FILE *tep;
} YeuCauTep;
static void xong(YeuCau *yc) {
YeuCauTep *t = VE_CHU(yc, YeuCauTep, yc);
fclose(t->tep);
free(t);
}
/* He thong su kien chi biet kieu "YeuCau", nhung ham goi lai
lay lai duoc toan bo ngu canh cua minh.
Day la cach thay the cho tham so "void *ngu_canh", va no co
uu diem: khong can cap phat rieng cho ngu canh. */Ba: nhóm nhiều cấp phát thành một
Struct có phần đuôi biến đổi
typedef struct {
size_t n;
double he_so;
char du_lieu[]; /* mang do dai bien doi, PHAI o cuoi */
} Goi;
Goi *goi_tao(size_t n) {
Goi *g = malloc(sizeof *g + n); /* MOT lan cap phat */
if (g == NULL) return NULL;
g->n = n;
g->he_so = 1.0;
return g;
}
/* So voi hai lan cap phat:
Goi *g = malloc(sizeof *g);
g->du_lieu = malloc(n);
Uu diem cua mot lan:
- mot lan malloc va mot lan free thay vi hai
- du lieu nam ngay canh tieu de, nen cung dong bo dem
- khong the quen free phan thu hai
Va sizeof(Goi) KHONG tinh "du_lieu": */
_Static_assert(sizeof(Goi) == 16, "tieu de phai la 16 byte");
_Static_assert(offsetof(Goi, du_lieu) == 16, "du lieu ngay sau tieu de");
/* Chu y: "offsetof(Goi, du_lieu)" co the LON HON sizeof(Goi) neu
phan tu cua mang can can chinh lon hon can chinh cua struct.
Nen luon dung offsetof, khong dung sizeof, khi tinh kich thuoc cap phat:
malloc(offsetof(Goi, du_lieu) + n * sizeof(g->du_lieu[0])) */Tự làm thử
- In
offsetofcủa mọi trường trong một struct và đối chiếu với phép tính bằng tay. - Viết
VE_CHUvà xem hợp ngữ của nó ở-O2. - Truyền nhầm tên trường cho
VE_CHUở cả hai bản và so cảnh báo. - Thêm bốn loại khẳng định bố cục vào một struct của bạn.
- Cài danh sách xâm nhập cho một kiểu và cho nó nằm trong hai danh sách cùng lúc.
- Viết struct có phần đuôi biến đổi và kiểm tra
offsetofso vớisizeof.
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
offsetoflà một macro cho biểu thức hằng lúc dịch, dùng được trong_Static_assert.container_oftrừ độ dời khỏi địa chỉ trường, và sinh ra đúng một lệnhlea.- Bản của nhân Linux dùng
__typeof__để bắt lỗi truyền nhầm tên trường. - Chốt bố cục bằng
_Static_assertchỉ khi nó là hợp đồng: định dạng tệp, ABI, thanh ghi, kế thừa thủ công. - Với struct có phần đuôi biến đổi, tính kích thước cấp phát bằng
offsetofchứ không bằngsizeof.