Bài 54.330 phút đọc
X Macro
Sau bài này bạn sẽ làm được
- Viết một danh sách X Macro hoàn chỉnh
- Sinh bốn thứ khác nhau từ một danh sách
- Thêm một mục mới và xác nhận mọi chỗ tự đồng bộ
- Kể năm ứng dụng khác của kỹ thuật này
Thêm một mã lỗi mới nghĩa là sửa bốn chỗ: enum, bảng mô tả, bảng tên, hàm phân tích. Quên một chỗ là lệch, và trình biên dịch không nói gì. X Macro giải quyết triệt để: khai báo một lần, sinh ra tất cả.
#Vấn đề: bốn chỗ phải sửa
Bốn danh sách phải giữ đồng bộ bằng tay
/* Cho 1: enum */
typedef enum {
LOI_OK = 0,
LOI_TEP = 1,
LOI_BONHO = 2,
LOI_THAMSO = 3,
LOI_SO_LUONG
} MaLoi;
/* Cho 2: bang mo ta */
static const char *const MO_TA[] = {
"Thanh cong",
"Khong mo duoc tep",
"Het bo nho",
"Tham so khong hop le",
};
/* Cho 3: bang ten */
static const char *const TEN[] = {
"LOI_OK", "LOI_TEP", "LOI_BONHO", "LOI_THAMSO",
};
/* Cho 4: ham phan tich */
MaLoi tu_ten(const char *s) {
if (strcmp(s, "LOI_OK") == 0) return LOI_OK;
if (strcmp(s, "LOI_TEP") == 0) return LOI_TEP;
if (strcmp(s, "LOI_BONHO") == 0) return LOI_BONHO;
if (strcmp(s, "LOI_THAMSO") == 0) return LOI_THAMSO;
return LOI_THAMSO;
}
/* Them LOI_MANG: phai sua CA BON cho.
Quen cho 2 -> MO_TA[4] doc ngoai bien.
Quen cho 3 -> TEN[4] doc ngoai bien.
Chen vao GIUA -> moi bang lech het.
Va khong canh bao nao. */Một danh sách, sinh ra tất cả
/* Khai bao MOT LAN */
#define DANH_SACH_LOI(X) \
X(LOI_OK, 0, "Thanh cong") \
X(LOI_TEP, 1, "Khong mo duoc tep") \
X(LOI_BONHO, 2, "Het bo nho") \
X(LOI_THAMSO, 3, "Tham so khong hop le")
/* Roi sinh ra bon thu tu no. Xem muc duoi.
Them LOI_MANG: THEM MOT DONG. Bon cho tu dong dong bo. */X Macro
Một danh sách được viết dưới dạng một macro nhận tên của một macro khác làm tham số. Bạn định nghĩa macro đó theo nhiều cách khác nhau và gọi danh sách nhiều lần, mỗi lần sinh ra một thứ.
#Cách làm
xmacro.c
#include <stdio.h>
#include <string.h>
/* --- 1. Danh sach, khai bao MOT LAN --- */
#define DANH_SACH_LOI(X) \
X(LOI_OK, 0, "Thanh cong") \
X(LOI_TEP, 1, "Khong mo duoc tep") \
X(LOI_BONHO, 2, "Het bo nho") \
X(LOI_THAMSO, 3, "Tham so khong hop le") \
X(LOI_MANG, 4, "Loi ket noi mang")
/* --- 2. Sinh enum --- */
typedef enum {
#define X(ten, ma, mota) ten = ma,
DANH_SACH_LOI(X)
#undef X
LOI_SO_LUONG
} MaLoi;
/* --- 3. Sinh bang mo ta --- */
static const char *const MO_TA[] = {
#define X(ten, ma, mota) [ma] = mota,
DANH_SACH_LOI(X)
#undef X
};
/* --- 4. Sinh bang ten --- */
static const char *const TEN[] = {
#define X(ten, ma, mota) [ma] = #ten,
DANH_SACH_LOI(X)
#undef X
};
/* --- 5. Sinh ham phan tich --- */
static MaLoi tu_ten(const char *s) {
#define X(ten, ma, mota) if (strcmp(s, #ten) == 0) return ten;
DANH_SACH_LOI(X)
#undef X
return LOI_THAMSO;
}#Chạy thử
main
int main(void) {
printf("so ma loi = %d\n\n", LOI_SO_LUONG);
for (int i = 0; i < LOI_SO_LUONG; ++i)
printf("%-12s %d %s\n", TEN[i], i, MO_TA[i]);
printf("\ntu_ten(\"LOI_BONHO\") = %d\n", tu_ten("LOI_BONHO"));
printf("tu_ten(\"KHONG_CO\") = %d\n", tu_ten("KHONG_CO"));
return 0;
}terminal
gcc -std=c11 -O2 -Wall -Wextra -o xmacro.exe xmacro.c && ./xmacro.exe
so ma loi = 5
LOI_OK 0 Thanh cong
LOI_TEP 1 Khong mo duoc tep
LOI_BONHO 2 Het bo nho
LOI_THAMSO 3 Tham so khong hop le
LOI_MANG 4 Loi ket noi mang
tu_ten("LOI_BONHO") = 2
tu_ten("KHONG_CO") = 3#Thêm một mục
Một dòng, và năm chỗ tự đồng bộ
#define DANH_SACH_LOI(X) \
X(LOI_OK, 0, "Thanh cong") \
X(LOI_TEP, 1, "Khong mo duoc tep") \
X(LOI_BONHO, 2, "Het bo nho") \
X(LOI_THAMSO, 3, "Tham so khong hop le") \
X(LOI_MANG, 4, "Loi ket noi mang") \
X(LOI_QUYEN, 5, "Khong du quyen truy cap") /* THEM DONG NAY */
/* Tu dong:
- LOI_QUYEN vao enum, gia tri 5
- LOI_SO_LUONG thanh 6
- MO_TA[5] co mo ta
- TEN[5] co chuoi "LOI_QUYEN"
- tu_ten nhan duoc "LOI_QUYEN"
- vong lap in ra sau dong thay vi nam
Khong the quen cho nao, vi khong co cho nao de quen. */#Năm ứng dụng khác
Một: bảng lệnh dòng lệnh
#define DANH_SACH_LENH(X) \
X(them, "Them mot ban ghi", xu_ly_them, 2) \
X(xoa, "Xoa mot ban ghi", xu_ly_xoa, 1) \
X(tim, "Tim theo ma", xu_ly_tim, 1) \
X(liet, "Liet ke tat ca", xu_ly_liet, 0)
/* Khai bao truoc cac ham */
#define X(ten, mo_ta, ham, so_doi) static int ham(char **, int);
DANH_SACH_LENH(X)
#undef X
typedef struct {
const char *ten;
const char *mo_ta;
int (*ham)(char **, int);
int so_doi;
} Lenh;
static const Lenh BANG_LENH[] = {
#define X(ten, mo_ta, ham, so_doi) { #ten, mo_ta, ham, so_doi },
DANH_SACH_LENH(X)
#undef X
};
/* Va ham tro giup tu dong day du: */
static void in_tro_giup(void) {
for (size_t i = 0; i < sizeof BANG_LENH / sizeof BANG_LENH[0]; ++i)
printf(" %-8s %s\n", BANG_LENH[i].ten, BANG_LENH[i].mo_ta);
}
/* Them mot lenh = them mot dong, va tro giup tu cap nhat. */Hai: bảng chuyển trạng thái
#define CAC_TRANG_THAI(X) X(CHO) X(CHAY) X(TAM_DUNG) X(DUNG)
#define CAC_TIN_HIEU(X) X(BAT) X(TAT) X(NGHI)
typedef enum {
#define X(t) TT_##t,
CAC_TRANG_THAI(X)
#undef X
TT_SO_LUONG
} TrangThai;
static const char *const TEN_TT[] = {
#define X(t) [TT_##t] = #t,
CAC_TRANG_THAI(X)
#undef X
};
/* Bai 29.4 da lam may trang thai bang tay. Voi X Macro thi
ten trang thai va enum khong bao gio lech. */Ba: tuần tự hóa struct
#define CAC_TRUONG_SV(X) \
X(uint32_t, ma, u32) \
X(uint16_t, tuoi, u16) \
X(double, diem, f64)
typedef struct {
#define X(kieu, ten, ma) kieu ten;
CAC_TRUONG_SV(X)
#undef X
} SinhVien;
size_t sv_dong_goi(const SinhVien *sv, uint8_t *ra, size_t cap) {
size_t vi_tri = 0;
#define X(kieu, ten, ma) \
if (vi_tri + sizeof(kieu) > cap) return 0; \
ghi_##ma(ra + vi_tri, sv->ten); \
vi_tri += sizeof(kieu);
CAC_TRUONG_SV(X)
#undef X
return vi_tri;
}
size_t sv_mo_goi(SinhVien *sv, const uint8_t *tu, size_t co) {
size_t vi_tri = 0;
#define X(kieu, ten, ma) \
if (vi_tri + sizeof(kieu) > co) return 0; \
sv->ten = doc_##ma(tu + vi_tri); \
vi_tri += sizeof(kieu);
CAC_TRUONG_SV(X)
#undef X
return vi_tri;
}
/* Them mot truong = them mot dong, va ca ba noi tu dong dong bo:
struct, ham dong goi, ham mo goi.
Bai 53.4 da noi vi sao phai tuan tu hoa tung truong. */Bốn: bảng thanh ghi nhúng
#define CAC_THANH_GHI(X) \
X(CR1, 0x00, "dieu khien 1") \
X(CR2, 0x04, "dieu khien 2") \
X(SR, 0x08, "trang thai") \
X(DR, 0x0C, "du lieu")
enum {
#define X(ten, do_doi, mo_ta) TG_##ten = do_doi,
CAC_THANH_GHI(X)
#undef X
};
/* Va mot ham do toan bo khoi thanh ghi de go loi: */
void do_thanh_ghi(volatile uint8_t *co_so) {
#define X(ten, do_doi, mo_ta) \
printf("%-4s 0x%02X = 0x%08lX %s\n", #ten, do_doi, \
(unsigned long)*(volatile uint32_t *)(co_so + do_doi), \
mo_ta);
CAC_THANH_GHI(X)
#undef X
}
/* Bai 47.3 cua Phan 18 da noi ve anh xa thanh ghi. */Năm: đăng ký bài kiểm thử
#define CAC_BAI_THU(X) \
X(thu_bitutils) \
X(thu_generic) \
X(thu_callback) \
X(thu_mmio)
/* Khai bao truoc */
#define X(f) void f(void);
CAC_BAI_THU(X)
#undef X
/* Bang */
static void (*const BAI_THU[])(void) = {
#define X(f) f,
CAC_BAI_THU(X)
#undef X
};
static const char *const TEN_THU[] = {
#define X(f) #f,
CAC_BAI_THU(X)
#undef X
};
int main(void) {
for (size_t i = 0; i < sizeof BAI_THU / sizeof BAI_THU[0]; ++i) {
printf("[%s]\n", TEN_THU[i]);
BAI_THU[i]();
}
...
}
/* Bai 32.4 goi tung ham thu bang tay trong main. Voi X Macro thi
them mot bai thu = them mot dong, va no khong the bi quen goi. */#Giới hạn
terminal
# Sai một chỗ trong danh sách, lỗi trỏ vào đâu
gcc -std=c11 -c xmacro_sai.c
xmacro_sai.c:12:34: error: expected ',' or '}' before ';' token
#define X(ten, ma, mota) ten = ma;
^
xmacro_sai.c:5:5: note: in expansion of macro 'X'
X(LOI_OK, 0, "Thanh cong")
xmacro_sai.c:13:5: note: in expansion of macro 'DANH_SACH_LOI'
DANH_SACH_LOI(X)Tự làm thử
- Viết danh sách X Macro cho mã lỗi và sinh ra đủ bốn thứ.
- Thêm một mã lỗi và xác nhận không phải sửa chỗ nào khác.
- Chèn một mục vào giữa danh sách và xác nhận mọi bảng vẫn đúng.
- Chạy
gcc -E -Pvà đọc mã thật sự được sinh ra. - Dùng X Macro để đăng ký bài kiểm thử trong dự án Bài 32.4.
- Viết cặp hàm đóng gói và mở gói bằng X Macro cho một struct bốn trườ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
- X Macro là một danh sách nhận tên một macro khác làm tham số, và bạn định nghĩa lại macro đó trước mỗi lần dùng.
- Luôn
#undef Xngay sau mỗi khối, nếu không lần dùng sau lấy nhầm định nghĩa cũ. - Kết hợp với khởi tạo có chỉ định
[ma] =để thứ tự dòng trong danh sách không còn quan trọng. - Ứng dụng: mã lỗi, bảng lệnh, bảng trạng thái, tuần tự hóa, bảng thanh ghi, đăng ký bài kiểm thử.
- Nhược điểm là thông báo lỗi và việc gỡ lỗi; ngưỡng chuyển sang chương trình sinh mã là khoảng một trăm mục.