Bài 19.726 phút đọc
Header guard và cách viết header
Sau bài này bạn sẽ làm được
- Giải thích lỗi định nghĩa trùng khi thiếu header guard
- Đặt tên guard theo quy ước tránh trùng trong dự án lớn
- So sánh include guard với pragma once
- Liệt kê thứ được phép và thứ bị cấm trong một tệp header
Ba dòng ở đầu và cuối mỗi tệp header trông như nghi thức thừa. Chúng không thừa: không có chúng thì một dự án chỉ cần ba tệp là đã không biên dịch được nữa.
#Vì sao include hai lần là lỗi
Ba tệp là đủ để hỏng
/* a.h */
struct S { int x; };
/* b.h */
#include "a.h"
/* m.c */
#include "a.h"
#include "b.h"
int main(void) { struct S s = { 1 }; return s.x - 1; }terminal
gcc -std=c11 -o m m.c
In file included from b.h:1,
from m.c:2:
a.h:1:8: error: redefinition of 'struct S'
struct S { int x; };
^
In file included from m.c:1:
a.h:1:8: note: originally defined here#Ba dòng bao quanh mọi header
sinhvien.h
#ifndef SINHVIEN_H
#define SINHVIEN_H
/* ... toan bo noi dung header ... */
#endif /* SINHVIEN_H */Header guard
Ba chỉ thị bao quanh toàn bộ nội dung một header. Lần include đầu tiên, macro chưa tồn tại nên nội dung được xử lý và macro được định nghĩa. Mọi lần sau, macro đã có nên preprocessor bỏ qua toàn bộ khối.
terminal
# Cùng ba tệp như trên, chỉ thêm guard vào a.h
gcc -std=c11 -o m2 m2.c && echo 'bien dich sach'
bien dich sach
/* Dien bien lan mot: */
#ifndef SINHVIEN_H -> SINHVIEN_H chua co -> vao trong
#define SINHVIEN_H -> gio no co roi
... noi dung duoc xu ly ...
#endif
/* Dien bien lan hai: */
#ifndef SINHVIEN_H -> SINHVIEN_H DA CO -> bo qua het
... preprocessor nuot toan bo, khong sinh mot token nao ...
#endif#Đặt tên guard
| Quy mô dự án | Kiểu tên | Ví dụ |
|---|---|---|
| Bài tập, một thư mục | TENTEP_H | SINHVIEN_H |
| Dự án có thư mục con | DUONGDAN_TENTEP_H | QUANLY_LUUTRU_CSV_H |
| Thư viện phát hành cho người khác | TENDUAN_DUONGDAN_TENTEP_H | SVLIB_CORE_STUDENT_H |
#pragma once
#pragma once
/* ... noi dung header ... */
/* Khong can #endif. */| Tiêu chí | Include guard | pragma once |
|---|---|---|
| Trong chuẩn C | Có | Không |
| Chạy trên mọi trình biên dịch | Có | Gần như, nhưng không đảm bảo |
| Số dòng phải viết | 3 | 1 |
| Trùng tên gây lỗi im lặng | Có | Không |
| Cùng tệp qua hai đường dẫn khác nhau | Nhận ra đúng | Có thể không nhận ra |
| Dùng được cho việc include cố ý nhiều lần | Có, bằng cách bỏ guard | Không |
#Cách viết một header tốt
sinhvien.h, thứ tự chuẩn
#ifndef SINHVIEN_H
#define SINHVIEN_H
/* 1. Include nhung gi HEADER NAY can, khong hon */
#include <stddef.h> /* size_t */
#include <stdint.h> /* uint16_t */
#include "loi.h" /* MaLoi */
/* 2. Hang so */
enum {
SV_MSSV_CO = 16,
SV_TEN_CO = 64,
SV_SO_MON = 5,
};
/* 3. Kieu du lieu */
typedef struct {
char mssv[SV_MSSV_CO];
char ho_ten[SV_TEN_CO];
uint16_t diem_x100[SV_SO_MON];
} SinhVien;
/* 4. Khai bao bien toan cuc, PHAI co extern, xem Bai 20.3 */
extern const char *const SV_TEN_XEP_LOAI[5];
/* 5. Nguyen mau ham cong khai */
MaLoi sv_kiem_tat_ca(const SinhVien *sv);
uint16_t sv_diem_tb_x100(const SinhVien *sv);
const char *sv_xep_loai(const SinhVien *sv);
/* 6. Ham nho, neu co, phai la static inline */
static inline int sv_dat(const SinhVien *sv) {
return sv_diem_tb_x100(sv) >= 500;
}
#endif /* SINHVIEN_H */terminal
# Định nghĩa biến trong header, hai tệp .c include nó
gcc -std=c11 -o xa x1.c x2.c
x2.o:x2.c:(.data+0x0): multiple definition of `g_muc_log' x1.o:x1.c:(.data+0x0): first defined here collect2: error: ld returned 1 exit status
Danh sách kiểm cho mỗi header
| Câu hỏi | Cách kiểm |
|---|---|
| Có header guard không | grep '^#ifndef' tệp.h |
| Tên guard có trùng tệp khác không | grep -rh '^#ifndef' | sort | uniq -d |
| Mọi thứ có nằm trong guard không | Nhìn dòng đầu và dòng cuối |
| Dịch được một mình không | Tạo tệp .c chỉ include nó rồi gcc -fsyntax-only |
| Có định nghĩa biến hoặc hàm nào không | grep -n '^[a-zA-Z].*=' và grep -n '^{' |
| Có include nào thừa không | Bỏ từng cái rồi build lại, xem Bài 20.5 |
Tự làm thử
- Tạo ba tệp
a.h,b.h,m.ckhông có guard và đọc thông báo lỗi. - Thêm guard vào
a.hvà xác nhận nó biên dịch sạch. - Cố ý đặt cùng tên guard cho hai header khác nhau, include cả hai, và xem triệu chứng.
- Viết vòng lặp shell kiểm tra mọi header trong dự án có dịch được một mình không.
- Đặt
int g_dem = 0;trong một header rồi include nó từ hai tệp.c, đọc lỗi của trình liên kết. - Bỏ một dòng
#includekhỏi header của bạn và xem có tệp nào hỏng 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
- Không có guard, chỉ cần một header include header khác là đã có định nghĩa
structhai lần. - Guard gồm ba dòng, và toàn bộ nội dung header phải nằm trong chúng, kể cả các dòng
#include. - Tên guard phải duy nhất trong dự án và không được bắt đầu bằng gạch dưới. Trùng tên gây lỗi im lặng rất khó tìm.
#pragma oncengắn hơn nhưng không nằm trong chuẩn. Chọn một kiểu và dùng nhất quán.- Header phải tự đủ: include những gì chính nó cần, và không include thứ chỉ tệp
.ccần. - Không bao giờ định nghĩa biến hay hàm không
statictrong header.