Bài 54.126 phút đọc
Macro hàm và các bẫy
Sau bài này bạn sẽ làm được
- Bọc ngoặc đúng chỗ cho mọi tham số và cho cả biểu thức
- Nhận ra bẫy đánh giá nhiều lần
- Dùng do while (0) cho macro dạng câu lệnh
- Biết khi nào static inline tốt hơn macro
Macro không phải hàm. Nó là phép thay thế văn bản, và bốn cái bẫy trong bài này đều đến từ chính điều đó. Ba cái đầu có cách chữa cơ học, cái thứ tư thì không.
#Bẫy một: thiếu ngoặc
Thiếu ngoặc
#define BINH(x) x * x
BINH(3 + 1) /* bung ra: 3 + 1 * 3 + 1 = 7, khong phai 16 */
#define NUA(x) (x) / 2
10 / NUA(4) /* bung ra: 10 / (4) / 2 = 1, khong phai 5 */
#define TOI_DA 10 + 5
int a[TOI_DA]; /* int a[10 + 5], tinh co dung */
int b = TOI_DA * 2; /* 10 + 5 * 2 = 20, khong phai 30 */Ngoặc đầy đủ
#define BINH(x) ((x) * (x))
BINH(3 + 1) /* ((3 + 1) * (3 + 1)) = 16 */
#define NUA(x) ((x) / 2)
10 / NUA(4) /* 10 / ((4) / 2) = 5 */
#define TOI_DA (10 + 5)
int b = TOI_DA * 2; /* (10 + 5) * 2 = 30 */terminal
# Xem macro bung ra thành gì
gcc -E -P macro.c
int main(void) {
int a = 3 + 1 * 3 + 1;
int b = ((3 + 1) * (3 + 1));
return a + b;
}#Bẫy hai: đánh giá nhiều lần
Ngoặc đủ vẫn sai
#define TOI_DA(a, b) ((a) > (b) ? (a) : (b))
int i = 0;
int x = TOI_DA(i++, 5);
/* Bung ra:
int x = ((i++) > (5) ? (i++) : (5));
i++ chay HAI lan neu nhanh dau duoc chon.
Va day cung la sua i nhieu lan trong mot cau lenh, tuc UB
theo Bai 33.5. */
int y = TOI_DA(f(), g());
/* f() co the chay hai lan. Neu f doc mot dong tu tep
thi ban vua mat mot dong du lieu. */| Đối số | Nguy hiểm | Vì sao |
|---|---|---|
| Biến thường, x | Không | Đọc nhiều lần vô hại |
| Hằng số, 5 | Không | Vô hại |
| i++ hoặc ++i | Có | Tác dụng phụ chạy nhiều lần |
| f() | Có | Hàm chạy nhiều lần |
| *p++ | Có | Con trỏ tiến nhiều lần |
| a[i++] | Có | Cả hai vấn đề |
| x + 1 | Không, nhưng chậm | Phép cộng tính nhiều lần |
#Bẫy ba: macro dạng câu lệnh
Nhiều câu lệnh không bọc
#define GHI_LOG(tin) \
dem_log++; \
printf("%s\n", tin)
if (co_loi)
GHI_LOG("co loi");
/* Bung ra:
if (co_loi)
dem_log++;
printf("%s\n", "co loi"); <- LUON chay!
Va te hon: */
if (co_loi)
GHI_LOG("co loi");
else
xu_ly();
/* error: 'else' without a previous 'if' */do while (0)
#define GHI_LOG(tin) do { \
dem_log++; \
printf("%s\n", tin); \
} while (0)
if (co_loi)
GHI_LOG("co loi");
else
xu_ly();
/* Bung ra:
if (co_loi)
do { dem_log++; printf(...); } while (0);
else
xu_ly();
Dung. Va dau cham phay o cuoi loi goi macro tro thanh
dau cham phay ket thuc "do while", tuc dung cu phap. */Ba dạng macro và cách bọc từng dạng
/* 1. Macro BIEU THUC: co gia tri, dung ngoac tron */
#define BINH(x) ((x) * (x))
int y = BINH(3) + 1;
/* 2. Macro CAU LENH: khong co gia tri, dung do while (0) */
#define DOI_CHO(a, b) do { \
int _t = (a); (a) = (b); (b) = _t; \
} while (0)
DOI_CHO(x, y);
/* 3. Macro KHAI BAO: khong bo gi ca, va nho KHONG co dau cham phay */
#define KHAI_BAO_VEC(T, ten) \
typedef struct { T *d; size_t n; } ten; \
int ten##_them(ten *v, T x);
KHAI_BAO_VEC(int, VecInt) /* khong dau cham phay */
/* Neu ban muon nguoi dung viet dau cham phay cho dep thi them
mot khai bao gia o cuoi: */
#define KHAI_BAO_VEC(T, ten) \
typedef struct { T *d; size_t n; } ten; \
int ten##_them(ten *v, T x); \
_Static_assert(1, "") /* nuot dau cham phay */
KHAI_BAO_VEC(int, VecInt); /* gio co dau cham phay, va dung */#Bẫy bốn: biến che khuất
Bẫy không có cách chữa cơ học
#define DOI_CHO(a, b) do { \
int _t = (a); (a) = (b); (b) = _t; \
} while (0)
int _t = 5, y = 10;
DOI_CHO(_t, y);
/* Bung ra:
do { int _t = (_t); (_t) = (y); (y) = _t; } while (0);
^^^^^^^^^^^
bien _t MOI che khuat bien _t cua ban,
va khoi tao no bang CHINH NO, tuc gia tri rac
Ket qua: hoan toan sai, va khong canh bao nao. */terminal
# Hoán đổi im lặng không làm gì cả
gcc -std=c11 -Wall -Wextra -Wshadow -o che.exe che.c && ./che.exe
che.c: In function 'main':
che.c:3:32: warning: declaration of '_t' shadows a previous local [-Wshadow]
#define DOI_CHO(a, b) do { int _t = (a); (a) = (b); (b) = _t; } while (0)
^~
che.c:8:5: note: in expansion of macro 'DOI_CHO'
DOI_CHO(_t, y);
^~~~~~~
che.c:6:9: note: shadowed declaration is here
5 10#Khi nào dùng static inline thay thế
| Macro | static inline | |
|---|---|---|
| Kiểm tra kiểu | Không | Có |
| Đánh giá đối số một lần | Không, trừ khi dùng thủ thuật | Có |
| Gỡ lỗi bằng GDB | Khó, cần -g3 | Dễ, bước vào được |
| Thông báo lỗi | Trỏ vào dòng #define | Trỏ đúng chỗ |
| Làm việc với nhiều kiểu | Có | Không, trừ khi dùng _Generic |
| Dùng được trong #if | Có | Không |
| Dùng làm kích thước mảng | Có, nếu là hằng | Không |
| Truy cập __FILE__ và __LINE__ của nơi gọi | Có | Không |
| Tốc độ | Bằng nhau ở -O2 | Bằng nhau ở -O2 |
Bốn quy tắc gói lại
/* 1. NGOAC quanh moi tham so VA quanh ca bieu thuc */
#define BINH(x) ((x) * (x))
/* 2. do while (0) cho macro dang cau lenh */
#define DOI_CHO(a, b) do { ... } while (0)
/* 3. TEN VIET HOA, va co tien to neu la thu vien */
#define ML_TOI_DA(a, b) ...
/* 4. Ghi vao chu thich neu macro danh gia doi so nhieu lan */
/* CANH BAO: danh gia a va b hai lan. Khong truyen bieu thuc
co tac dung phu. */
#define ML_TOI_DA(a, b) ((a) > (b) ? (a) : (b))Tự làm thử
- Viết
BINH(3 + 1)không ngoặc và xem nó bung ra bằnggcc -E -P. - Truyền
i++cho một macro đánh giá hai lần và in giá trịisau đó. - Viết macro nhiều câu lệnh không bọc và dùng nó trong
if else. - Đặt tên biến trùng với biến tạm của macro và chạy với
-Wshadow. - Viết cùng một thứ bằng macro và bằng
static inline, so hợp ngữ ở-O2. - Tìm trong một dự án mọi macro dài hơn năm dòng và xem có thay được bằng hàm khô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
- Ngoặc quanh mọi tham số và quanh cả biểu thức. Hai lớp chống hai loại lỗi ưu tiên khác nhau.
- Ngoặc đủ vẫn không cứu được bẫy đánh giá nhiều lần; chỉ biểu thức khối hoặc hàm mới cứu được.
do while (0)biến nhiều câu lệnh thành một câu lệnh đơn cần dấu chấm phẩy, giống hệt lời gọi hàm.- Biến tạm trong macro có thể che khuất biến của người dùng, và không có cách chữa cơ học nào ngoài đặt tên khó trùng.
- Bốn trường hợp macro là đúng: cần
__LINE__của nơi gọi, cần chuỗi hóa, cần nhiều kiểu, cần hằng số dùng trong#if.