Bài 54.426 phút đọc
Thủ thuật và cách gỡ lỗi macro
Sau bài này bạn sẽ làm được
- Viết ARRAY_LEN báo lỗi khi truyền con trỏ
- Sinh tên biến duy nhất bằng __LINE__
- Xem macro bung ra bằng gcc -E
- Biết khi nào nên dừng lại và không dùng macro nữa
Bốn thủ thuật cuối cùng, rồi một mục nói về chuyện quan trọng hơn tất cả: khi nào không nên dùng macro nữa. Bộ tiền xử lý mạnh tới mức rất dễ đi quá xa.
#ARRAY_LEN an toàn
Bản thường
#define MANG_LEN(a) (sizeof(a) / sizeof((a)[0]))
int a[10];
MANG_LEN(a) /* 10, dung */
void f(int a[10]) {
MANG_LEN(a) /* 2 tren may 64 bit!
Vi tham so mang phan ra thanh con tro,
nen sizeof(a) la 8 va sizeof(a[0]) la 4 */
}
int *p = malloc(10 * sizeof *p);
MANG_LEN(p) /* 2, va khong canh bao nao */Bản kiểm tra
/* Ban bao loi khi truyen con tro, tren GCC va Clang */
#define LA_MANG(a) \
__builtin_types_compatible_p(__typeof__(a), __typeof__(&(a)[0]))
#define MANG_LEN(a) \
(sizeof(a) / sizeof((a)[0]) \
+ sizeof(char[1 - 2 * LA_MANG(a)]) - 1)
int a[10];
MANG_LEN(a) /* 10 */
int *p;
MANG_LEN(p) /* error: size of unnamed array is negative */terminal
# GCC 8.1 báo cả hai: một cảnh báo và một lỗi
gcc -std=c11 -Wall -c manglen.c
manglen.c: In function 'f':
manglen.c:7:16: warning: division 'sizeof (int *) / sizeof (int)' does not compute the number of array elements [-Wsizeof-pointer-div]
(sizeof(a) / sizeof((a)[0]) + sizeof(char[1 - 2 * LA_MANG(a)]) - 1)
^
manglen.c:14:12: note: in expansion of macro 'MANG_LEN'
return MANG_LEN(p);
manglen.c:7:46: error: size of unnamed array is negative
(sizeof(a) / sizeof((a)[0]) + sizeof(char[1 - 2 * LA_MANG(a)]) - 1)
^#Tên biến duy nhất
Bài 54.1 để lại một vấn đề chưa giải
#define DOI_CHO(a, b) do { int _t = (a); (a) = (b); (b) = _t; } while (0)
int _t = 5, y = 10;
DOI_CHO(_t, y); /* im lang khong lam gi, Bai 54.1 */
/* Cach chua: sinh ten bien theo SO DONG */
#define NOI2(a, b) a##b
#define NOI(a, b) NOI2(a, b) /* hai tang, Bai 54.2 */
#define DUY_NHAT(x) NOI(x, __LINE__)
#define DOI_CHO(a, b) do { \
int DUY_NHAT(_dc) = (a); \
(a) = (b); \
(b) = DUY_NHAT(_dc); \
} while (0)
int _t = 5, y = 10;
DOI_CHO(_t, y); /* sinh bien "_dc42" neu o dong 42 */Ứng dụng: khối dọn dẹp tự động
/* Mau "mo, lam viec, tu dong dong" bang mot vong lap chay mot lan */
#define VOI_TEP(bien, ten, che_do) \
for (FILE *bien = fopen(ten, che_do), *DUY_NHAT(_c) = bien; \
DUY_NHAT(_c); \
fclose(bien), DUY_NHAT(_c) = NULL)
VOI_TEP(f, "a.txt", "r") {
char dong[256];
while (fgets(dong, sizeof dong, f)) fputs(dong, stdout);
}
/* fclose tu chay khi ra khoi khoi */
/* CANH BAO ba dieu:
1. "break" trong khoi se thoat vong lap MA KHONG chay fclose
2. "return" trong khoi cung vay
3. fopen that bai thi bien la NULL va khoi khong chay,
nen ban khong biet la co loi
Nen mau nay tien nhung NGUY HIEM. C khong co destructor,
va moi no bat chuoc deu co lo hong.
Cach an toan hon: goto va mot nhan don dep, Bai 20.6. */#Kiểm tra kiểu trong macro
/* 1. _Generic, C11 */
#define KIEM_KIEU(x, T) \
_Static_assert(_Generic((x), T: 1, default: 0), "sai kieu")
void f(int x) {
KIEM_KIEU(x, int); /* dat */
KIEM_KIEU(x, double); /* error: static assertion failed: "sai kieu" */
}
/* 2. __builtin_types_compatible_p, GNU */
#define CUNG_KIEU(a, b) \
__builtin_types_compatible_p(__typeof__(a), __typeof__(b))
_Static_assert(CUNG_KIEU(x, y), "hai bien phai cung kieu");
/* 3. Thu thuat "1 ? a : b", chuan hoan toan */
#define EP_AN_TOAN(T, x) ((T)(1 ? (x) : (T)0))
/* Neu x khong chuyen duoc sang T thi hai nhanh cua ?: khong co
kieu chung, va trinh bien dich bao loi.
Bai 53.3 da dung ky thuat nay cho container_of. */
/* 4. Kiem tra con tro khong phai void * */
#define KHONG_VOID(p) \
_Static_assert(!__builtin_types_compatible_p(__typeof__(p), void *), \
"can con tro co kieu")#Bốn lệnh gỡ lỗi macro
# 1. Xem ma sau khi tien xu ly
gcc -E file.c # ke ca #line va noi dung header
gcc -E -P file.c # BO dong #line, de doc hon
gcc -E -P file.c | grep -A5 MACRO_CUA_BAN
# Meo: dua qua mot bo dinh dang cho de doc
gcc -E -P file.c | clang-format
# 2. Liet ke MOI macro da dinh nghia
gcc -dM -E file.c # ke ca macro cua he thong
gcc -dM -E - < /dev/null # chi macro dinh nghia san
gcc -dM -E file.c | grep MY_ # loc theo tien to
# 3. Canh bao khi dung macro chua dinh nghia trong #if
gcc -Wundef file.c
# warning: "CO_TINH_NANG" is not defined, evaluates to 0
# 4. Trong GDB, voi -g3
(gdb) info macro TOI_DA
(gdb) macro expand BINH(3)
(gdb) print TOI_DAterminal
gcc -E -P macro.c
int main(void) {
int a = 3 + 1 * 3 + 1;
int b = ((3 + 1) * (3 + 1));
return a + b;
}GDB với -g3
$ gcc -g3 -O0 -o prog prog.c
$ gdb -q ./prog
(gdb) info macro TOI_DA
Defined at /home/a/prog.c:3
#define TOI_DA 100
(gdb) macro expand BINH(3)
expands to: ((3) * (3))
(gdb) print TOI_DA
$1 = 100
/* Voi -g thuong thi ca ba lenh nay deu bao "no symbol".
Chi -g3 moi luu thong tin macro. Bai 35.1. */#Khi nào nên dừng lại
| Nếu bạn cần | Dùng cái này thay vì macro |
|---|---|
| Một hàm nhỏ, nhanh | static inline. Bài 54.1 |
| Cùng một hàm cho vài kiểu | _Generic bọc vài hàm. Bài 51.3 |
| Cùng một hàm cho mọi kiểu | void * và con trỏ hàm. Bài 30.1 |
| Một hằng số | enum, hoặc constexpr từ C23. Bài 32.2 |
| Sinh mã cho nhiều kiểu | X Macro, hoặc chương trình sinh mã. Bài 54.3 |
| Đa hình | Bảng con trỏ hàm. Bài 29.5 |
| Cấu hình lúc dịch | Macro, đây đúng là việc của nó |
| Thông tin về nơi gọi | Macro, không có cách khác |
Tự làm thử
- Viết
MANG_LENbản kiểm tra và truyền cho nó một con trỏ. - Viết
DUY_NHATbằng__LINE__rồi thử đặt hai lời gọi trên cùng một dòng. - Sửa bằng
__COUNTER__và xác nhận nó hoạt động. - Viết
KIEM_KIEUbằng_Genericvà cố tình truyền sai kiểu. - Chạy
gcc -dM -Evà đếm số macro trình biên dịch định nghĩa sẵn. - Tìm macro phức tạp nhất trong một dự án và thử viết lại bằng hàm.
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
MANG_LENbản thường cho ra 2 khi nhận con trỏ;__builtin_types_compatible_pbiến việc đó thành lỗi dịch.__LINE__sinh tên duy nhất trừ khi hai lời gọi cùng một dòng;__COUNTER__giải quyết triệt để.- Macro được phép làm một thao tác ngắn hơn, nhưng không được làm nó vô hình.
gcc -E -Pđể xem macro bung ra,gcc -dM -Eđể liệt kê macro,-Wundefđể bắt tên gõ nhầm trong#if.- Bốn việc chỉ macro làm được: lấy
__LINE__nơi gọi, chuỗi hóa, sinh tên định danh, và cấu hình lúc dịch.