Bỏ qua điều hướng, tới nội dung chính
Học C
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

Chỉ cần một header include header khác là cùng một tệp tới nơi theo hai đường.
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ự ánKiểu tênVí dụ
Bài tập, một thư mụcTENTEP_HSINHVIEN_H
Dự án có thư mục conDUONGDAN_TENTEP_HQUANLY_LUUTRU_CSV_H
Thư viện phát hành cho người khácTENDUAN_DUONGDAN_TENTEP_HSVLIB_CORE_STUDENT_H

#pragma once

#pragma once

/* ... noi dung header ... */

/* Khong can #endif. */
Tiêu chíInclude guardpragma once
Trong chuẩn CCóKhông
Chạy trên mọi trình biên dịchCóGần như, nhưng không đảm bảo
Số dòng phải viết31
Trùng tên gây lỗi im lặngCóKhông
Cùng tệp qua hai đường dẫn khác nhauNhận ra đúngCó thể không nhận ra
Dùng được cho việc include cố ý nhiều lầnCó, bằng cách bỏ guardKhô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ỏiCách kiểm
Có header guard khônggrep '^#ifndef' tệp.h
Tên guard có trùng tệp khác khônggrep -rh '^#ifndef' | sort | uniq -d
Mọi thứ có nằm trong guard khôngNhìn dòng đầu và dòng cuối
Dịch được một mình khôngTạ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ônggrep -n '^[a-zA-Z].*=' và grep -n '^{'
Có include nào thừa khôngBỏ từng cái rồi build lại, xem Bài 20.5

Tự làm thử

  1. Tạo ba tệp a.h, b.h, m.c không có guard và đọc thông báo lỗi.
  2. Thêm guard vào a.h và xác nhận nó biên dịch sạch.
  3. Cố ý đặt cùng tên guard cho hai header khác nhau, include cả hai, và xem triệu chứng.
  4. 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.
  5. Đặ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.
  6. Bỏ một dòng #include khỏ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 struct hai 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 once ngắ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 .c cần.
  • Không bao giờ định nghĩa biến hay hàm không static trong header.