Bỏ qua điều hướng, tới nội dung chính
Học C
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ắcHệ quả
1Thành viên xếp theo đúng thứ tự khai báoTrường khai báo trước có độ dời nhỏ hơn
2Mỗ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
3Căn chỉnh của struct bằng căn chỉnh lớn nhất trong các thành viênstruct chứa double thì căn chỉnh 8
4sizeof(struct) chia hết cho căn chỉnh của nóCó thể phải chèn đệm Ở CUỐI
5Không có đệm TRƯỚC thành viên đầu tiênoffsetof 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) = 8
Bả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 uu

Tự làm thử

  1. Tính bằng tay sizeof của năm struct rồi kiểm chứng bằng chương trình.
  2. Vẽ bản đồ byte cho một struct có bốn trường khác kiểu.
  3. Chứng minh memcmp trên struct có đệm không đáng tin.
  4. Chạy -Wpadded trên một dự án và đếm số struct bị cảnh báo.
  5. Dùng ptype/o của GDB trên ba struct của bạn.
  6. Cài pahole trong 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, sizeof chia 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 memcmp và 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.