Bài 19.628 phút đọc
Biên dịch có điều kiện
Sau bài này bạn sẽ làm được
- Dùng ifdef, if defined, elif và error để chọn nhánh mã theo nền tảng
- Xây hệ thống nhật ký biến mất hoàn toàn ở bản phát hành
- Nêu vì sao không được đặt mã có tác dụng phụ trong assert
- Dùng Static_assert để kiểm tra giả định ngay lúc biên dịch
Biên dịch có điều kiện cho phép một mã nguồn chạy trên Windows lẫn Linux, và cho phép hệ thống nhật ký của bạn biến mất không còn một lệnh nào ở bản phát hành. Nó cũng là công cụ dễ bị lạm dụng nhất trong C.
#Cú pháp và bốn dạng điều kiện
#if bieu_thuc_hang_nguyen
#ifdef TEN /* tuong duong #if defined(TEN) */
#ifndef TEN /* tuong duong #if !defined(TEN) */
#elif bieu_thuc
#else
#endif| Dạng | Đúng khi | Dùng cho |
|---|---|---|
| #ifdef TEN | TEN đã được define, bất kể giá trị | Bật tắt chức năng bằng cờ -D |
| #ifndef TEN | TEN chưa được define | Header guard, giá trị mặc định |
| #if TEN > 3 | Biểu thức hằng nguyên khác không | So sánh số phiên bản |
| #if defined(A) && !defined(B) | Kết hợp nhiều điều kiện | Phát hiện nền tảng |
#Phát hiện nền tảng
nen-tang.h
#ifndef NEN_TANG_H
#define NEN_TANG_H
#if defined(_WIN32)
#include <windows.h>
#define PHAN_CACH_DUONG_DAN '\\'
#define NGU_MS(ms) Sleep(ms)
#elif defined(__linux__) || defined(__APPLE__)
#include <unistd.h>
#define PHAN_CACH_DUONG_DAN '/'
#define NGU_MS(ms) usleep((ms) * 1000u)
#else
#error "He dieu hanh khong duoc ho tro"
#endif
#endif /* NEN_TANG_H */| Macro | Đúng khi | Ghi chú |
|---|---|---|
| _WIN32 | Mọi bản Windows | Đúng cả trên Windows 64 bit |
| _WIN64 | Windows 64 bit | Khi đúng thì _WIN32 cũng đúng |
| __linux__ | Linux | Hai gạch dưới ở cả hai đầu |
| __APPLE__ | macOS và iOS | Kèm TARGET_OS_MAC để phân biệt |
| __unix__ | Hầu hết hệ Unix | Kể cả Linux |
| __GNUC__ | GCC hoặc Clang | Clang cũng đặt macro này |
| __clang__ | Chỉ Clang | Kiểm cái này trước __GNUC__ |
| _MSC_VER | Trình biên dịch của Microsoft | Giá trị là số phiên bản |
| __STDC_VERSION__ | Luôn có từ C95 | 201112L là C11 |
#Hệ thống nhật ký tắt được
ghi.h
#ifndef GHI_H
#define GHI_H
#include <stdio.h>
#ifdef GO_LOI
#define GHI(fmt, ...) \
fprintf(stderr, "[%s:%d] " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
#else
#define GHI(fmt, ...) ((void)0)
#endif
#endif /* GHI_H */terminal
# Không có -DGO_LOI
gcc -std=c11 -Wall -Wextra -O2 -o a a.c && ./a
chuong trinh chay xong, x = 42
# Có -DGO_LOI
gcc -std=c11 -Wall -Wextra -O2 -DGO_LOI -o b a.c && ./b
chuong trinh chay xong, x = 42 [a.c:12] Bat dau [a.c:13] Gia tri x = 42
# Nó tốn bao nhiêu khi tắt
gcc -O2 -c -o a.o a.c && gcc -O2 -DGO_LOI -c -o b.o a.c && size a.o b.o
text data bss dec hex filename
164 0 0 164 a4 a.o
292 0 0 292 124 b.o#assert và cái bẫy NDEBUG
#include <assert.h>
void xu_ly(int *p, size_t n) {
assert(p != NULL);
assert(n > 0);
/* ... */
}assert
Macro kiểm tra một điều kiện lúc chạy. Nếu điều kiện sai, nó in tên tệp, số dòng, biểu thức, rồi gọi
abort. Nếu macro NDEBUG được định nghĩa, assert biến mất hoàn toàn.terminal
gcc -std=c11 -o a a.c && ./a
doc_file duoc goi 1 lan Assertion failed! Program: a.exe File: a.c, Line 9 Expression: p != NULL
echo $?
3
#Static_assert kiểm lúc biên dịch
_Static_assert(bieu_thuc_hang, "thong bao"); /* C11 */
/* Tu C23 va trong <assert.h> tu C11: */
static_assert(bieu_thuc_hang, "thong bao");terminal
cat sa.c
#include <stdint.h>
typedef struct { char ma[16]; int32_t diem; } BG;
_Static_assert(sizeof(int) == 4, "Ma nay yeu cau int 4 byte");
_Static_assert(sizeof(BG) == 24, "Bo cuc BG thay doi, tep cu se hong");
int main(void) { return 0; }gcc -std=c11 -fsyntax-only sa.c
sa.c:4:1: error: static assertion failed: "Bo cuc BG thay doi, tep cu se hong" _Static_assert(sizeof(BG) == 24, "Bo cuc BG thay doi, tep cu se hong"); ^~~~~~~~~~~~~~
| assert | _Static_assert | |
|---|---|---|
| Kiểm khi nào | Lúc chạy | Lúc biên dịch |
| Điều kiện có thể là gì | Biểu thức bất kỳ | Chỉ hằng số biên dịch |
| Thất bại thì sao | Chương trình abort | Không dịch được |
| Chi phí lúc chạy | Một phép so sánh | Không có gì |
| Bị NDEBUG tắt | Có | Không |
| Dùng được sizeof | Được | Được |
Tự làm thử
- Viết
#if PHIEN_BAM >= 3gõ sai tên rồi biên dịch có và không có-Wundef, so sánh kết quả. - Xây header nền tảng tách
_WIN32với__linux__và xác nhận mã nghiệp vụ không cònifdefnào. - Viết macro
GHIcó hai nhánh, biên dịch hai bản, và so số bytetextbằng lệnhsize. - Đặt
assert(doc_file(f) == 0)rồi biên dịch với-DNDEBUGvà đếm số lầndoc_filechạy. - Thêm
_Static_assert(sizeof(SinhVien) == 112, ...)vào dự án Phần 8, rồi thêm một trường vào struct và xem điều gì xảy ra.
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
- Tên chưa định nghĩa trong
#ifbằng 0 và không có cảnh báo, nên hãy bật-Wundef. #ifchỉ hiểu số nguyên. Không có số thực, không có chuỗi, không cósizeof.- Gom mọi khác biệt nền tảng vào một header, đừng rải
ifdefgiữa mã nghiệp vụ. - Macro nhật ký định nghĩa thành
((void)0)biến mất hoàn toàn, còn hàm rỗng thì vẫn phải tính đối số. - Không đặt mã có tác dụng phụ trong
assert, vì-DNDEBUGxóa cả biểu thức bên trong. _Static_assertkiểm lúc biên dịch và dùng đượcsizeof, thứ mà#ifkhông có.