Project: thư viện tiện ích hệ thống
Sau bài này bạn sẽ làm được
- Thiết kế bốn module bitutils, generic, callback và mmio
- Viết khung kiểm thử nhỏ bằng macro
- Chạy sạch với bộ cờ cảnh báo nghiêm ngặt
- Chạy qua danh sách nghiệm thu của cả phần
Bốn module, một khung kiểm thử, một Makefile. Mỗi module gói lại đúng một chương của Phần 12, và cả bốn phải chạy sạch với bộ cờ cảnh báo nghiêm ngặt nhất.
#Đề bài và cấu trúc
Viết một thư viện tiện ích hệ thống dùng lại được, gồm bốn module độc lập. Mỗi module có một tệp .h công khai và một tệp .c riêng tư. Một chương trình kiểm thử duy nhất chạy qua tất cả.
lib/
test.h khung kiem thu bang macro
test.c hai bien dem toan cuc
bitutils.h thao tac bit va tap bit dong <- Chuong 31
bitutils.c
generic.h vector tong quat va danh sach <- Chuong 30
generic.c
callback.h hang doi su kien va may trang thai <- Chuong 29
callback.c
mmio.h khoi thanh ghi anh xa bo nho <- Chuong 32
mmio.c
main.c goi het cac ham thu
Makefile| Module | Nội dung | Khái niệm chính |
|---|---|---|
| callback | Hàng đợi sự kiện, bảng điều phối, máy trạng thái | Con trỏ hàm, ngữ cảnh void *, bảng tra hằng |
| generic | Vector void *, vector sinh bằng macro, danh sách xâm nhập | void *, sinh mã, container_of, qsort |
| bitutils | Thao tác bit, hàm dựng sẵn, bitset động | Toán tử bit, popcount, ctz, mảng uint64_t |
| mmio | Khối thanh ghi giả lập, đặt và xóa bit nguyên tử | volatile, const volatile, BSRR |
#Khung kiểm thử bằng macro
#ifndef TEST_H
#define TEST_H
#include <stdio.h>
#include <string.h>
extern int test_so_kiem;
extern int test_so_hong;
#define KIEM(dk) \
do { \
++test_so_kiem; \
if (!(dk)) { \
++test_so_hong; \
printf(" HONG %s:%d: %s\n", __FILE__, __LINE__, #dk); \
} \
} while (0)
#define KIEM_BANG(a, b) \
do { \
long long _a = (long long)(a), _b = (long long)(b); \
++test_so_kiem; \
if (_a != _b) { \
++test_so_hong; \
printf(" HONG %s:%d: %s == %s (%lld vs %lld)\n", \
__FILE__, __LINE__, #a, #b, _a, _b); \
} \
} while (0)
#define KIEM_CHUOI(a, b) \
do { \
++test_so_kiem; \
if (strcmp((a), (b)) != 0) { \
++test_so_hong; \
printf(" HONG %s:%d: \"%s\" vs \"%s\"\n", \
__FILE__, __LINE__, (a), (b)); \
} \
} while (0)
#define NHOM(ten) printf("[%s]\n", ten)
#endif#include "test.h"
int test_so_kiem = 0;
int test_so_hong = 0;#Module bitutils
#ifndef BITUTILS_H
#define BITUTILS_H
#include <stddef.h>
#include <stdint.h>
/* --- thao tac bit tren mot so --- */
#define BIT(n) (1u << (n))
#define BAT(x, n) ((x) |= BIT(n))
#define TAT(x, n) ((x) &= ~BIT(n))
#define DAO(x, n) ((x) ^= BIT(n))
#define CO(x, n) (((x) >> (n)) & 1u)
int bu_dem_bit(uint32_t x);
int bu_bit_cao(uint32_t x);
int bu_bit_thap(uint32_t x);
uint32_t bu_tron_len(uint32_t x);
uint32_t bu_dao_byte(uint32_t x);
/* --- tap bit dong --- */
typedef struct {
uint64_t *tu;
size_t so_bit;
} Bitset;
int bs_tao(Bitset *b, size_t so_bit);
void bs_huy(Bitset *b);
void bs_bat(Bitset *b, size_t i);
void bs_tat(Bitset *b, size_t i);
int bs_co(const Bitset *b, size_t i);
size_t bs_dem(const Bitset *b);
long bs_bit_bat_tiep(const Bitset *b, size_t tu_vi_tri);
#endif#include "bitutils.h"
#include <stdlib.h>
#include <string.h>
int bu_dem_bit(uint32_t x) {
#if defined(__GNUC__)
return __builtin_popcount(x);
#else
int n = 0;
while (x) { x &= x - 1; ++n; }
return n;
#endif
}
int bu_bit_cao(uint32_t x) {
if (x == 0) return -1;
#if defined(__GNUC__)
return 31 - __builtin_clz(x);
#else
int n = 0;
while (x >>= 1) ++n;
return n;
#endif
}
int bu_bit_thap(uint32_t x) {
if (x == 0) return -1;
#if defined(__GNUC__)
return __builtin_ctz(x);
#else
return bu_dem_bit((x & (uint32_t)(-(int32_t)x)) - 1);
#endif
}
uint32_t bu_tron_len(uint32_t x) {
if (x == 0) return 1;
--x;
x |= x >> 1; x |= x >> 2; x |= x >> 4;
x |= x >> 8; x |= x >> 16;
return x + 1;
}
uint32_t bu_dao_byte(uint32_t x) {
return ((x & 0x000000FFu) << 24) | ((x & 0x0000FF00u) << 8)
| ((x & 0x00FF0000u) >> 8) | ((x & 0xFF000000u) >> 24);
}#define SO_TU(n) (((n) + 63) / 64)
int bs_tao(Bitset *b, size_t so_bit) {
b->tu = calloc(SO_TU(so_bit), sizeof *b->tu);
if (b->tu == NULL) { b->so_bit = 0; return -1; }
b->so_bit = so_bit;
return 0;
}
void bs_huy(Bitset *b) { free(b->tu); b->tu = NULL; b->so_bit = 0; }
void bs_bat(Bitset *b, size_t i) { b->tu[i >> 6] |= (1ULL << (i & 63)); }
void bs_tat(Bitset *b, size_t i) { b->tu[i >> 6] &= ~(1ULL << (i & 63)); }
int bs_co(const Bitset *b, size_t i) {
return (int)((b->tu[i >> 6] >> (i & 63)) & 1ULL);
}
size_t bs_dem(const Bitset *b) {
size_t n = 0;
for (size_t i = 0; i < SO_TU(b->so_bit); ++i)
n += (size_t)__builtin_popcountll(b->tu[i]);
return n;
}
long bs_bit_bat_tiep(const Bitset *b, size_t tu_vi_tri) {
size_t so_tu = SO_TU(b->so_bit);
size_t i = tu_vi_tri >> 6;
if (i >= so_tu) return -1;
/* Che di cac bit truoc tu_vi_tri trong tu dau tien. */
uint64_t t = b->tu[i] & (~0ULL << (tu_vi_tri & 63));
while (t == 0) {
if (++i >= so_tu) return -1;
t = b->tu[i];
}
return (long)(i * 64 + (size_t)__builtin_ctzll(t));
}#Module generic
#ifndef GENERIC_H
#define GENERIC_H
#include <stddef.h>
/* --- vector tong quat bang void * --- */
typedef struct {
void *du_lieu;
size_t co_phan_tu;
size_t so;
size_t suc_chua;
} Vec;
int vec_tao(Vec *v, size_t co_phan_tu);
void vec_huy(Vec *v);
int vec_them(Vec *v, const void *pt);
void *vec_tai(const Vec *v, size_t i);
size_t vec_so(const Vec *v);
/* --- sinh ma bang macro, an toan kieu --- */
#define KHAI_BAO_VEC(T, ten) \
typedef struct { T *du_lieu; size_t so, suc_chua; } ten; \
int ten##_them(ten *v, T x); \
void ten##_huy(ten *v);
#define CAI_DAT_VEC(T, ten) \
int ten##_them(ten *v, T x) { \
if (v->so == v->suc_chua) { \
size_t sc = v->suc_chua ? v->suc_chua * 2 : 4; \
T *m = realloc(v->du_lieu, sc * sizeof *m); \
if (m == NULL) return -1; \
v->du_lieu = m; v->suc_chua = sc; \
} \
v->du_lieu[v->so++] = x; \
return 0; \
} \
void ten##_huy(ten *v) { \
free(v->du_lieu); v->du_lieu = NULL; v->so = v->suc_chua = 0; \
}
/* --- danh sach lien ket kieu nhan --- */
typedef struct Nut { struct Nut *truoc, *sau; } Nut;
#define NUT_KHOI_TAO(n) ((n)->truoc = (n)->sau = (n))
void nut_them_duoi(Nut *dau, Nut *moi);
void nut_go(Nut *n);
#define VE_CHU(ptr, T, thanh_vien) \
((T *)((char *)(ptr) - offsetof(T, thanh_vien)))
#define DUYET(bien, dau) \
for (Nut *bien = (dau)->sau; bien != (dau); bien = bien->sau)
#endif#include "generic.h"
#include <stdlib.h>
#include <string.h>
int vec_tao(Vec *v, size_t co_phan_tu) {
v->du_lieu = NULL;
v->co_phan_tu = co_phan_tu;
v->so = v->suc_chua = 0;
return co_phan_tu > 0 ? 0 : -1;
}
void vec_huy(Vec *v) {
free(v->du_lieu);
v->du_lieu = NULL;
v->so = v->suc_chua = 0;
}
int vec_them(Vec *v, const void *pt) {
if (v->so == v->suc_chua) {
size_t sc = v->suc_chua ? v->suc_chua * 2 : 4;
void *m = realloc(v->du_lieu, sc * v->co_phan_tu);
if (m == NULL) return -1; /* v->du_lieu con nguyen */
v->du_lieu = m;
v->suc_chua = sc;
}
memcpy((char *)v->du_lieu + v->so * v->co_phan_tu, pt, v->co_phan_tu);
++v->so;
return 0;
}
void *vec_tai(const Vec *v, size_t i) {
if (i >= v->so) return NULL;
return (char *)v->du_lieu + i * v->co_phan_tu;
}
size_t vec_so(const Vec *v) { return v->so; }
void nut_them_duoi(Nut *dau, Nut *moi) {
moi->sau = dau;
moi->truoc = dau->truoc;
dau->truoc->sau = moi;
dau->truoc = moi;
}
void nut_go(Nut *n) {
n->truoc->sau = n->sau;
n->sau->truoc = n->truoc;
n->truoc = n->sau = n; /* go roi van hop le, go lan hai vo hai */
}#Module callback
#ifndef CALLBACK_H
#define CALLBACK_H
#include <stddef.h>
/* --- hang doi su kien voi ham goi lai mang ngu canh --- */
typedef struct {
int loai;
int gia_tri;
} SuKien;
typedef void (*HamXuLy)(const SuKien *sk, void *ngu_canh);
enum { CB_TOI_DA = 8 };
typedef struct {
HamXuLy ham[CB_TOI_DA];
void *ngu_canh[CB_TOI_DA];
size_t so;
} Bus;
void bus_khoi_tao(Bus *b);
int bus_dang_ky(Bus *b, HamXuLy f, void *ngu_canh);
void bus_phat(Bus *b, const SuKien *sk);
/* --- may trang thai bang bang dieu phoi --- */
typedef enum { TT_CHO, TT_CHAY, TT_DUNG, TT_SO_LUONG } TrangThai;
typedef enum { SK_BAT, SK_TAT, SK_SO_LUONG } TinHieu;
TrangThai mtt_chuyen(TrangThai tt, TinHieu th);
const char *mtt_ten(TrangThai tt);
#endif#include "callback.h"
void bus_khoi_tao(Bus *b) { b->so = 0; }
int bus_dang_ky(Bus *b, HamXuLy f, void *ngu_canh) {
if (b->so == CB_TOI_DA) return -1;
b->ham[b->so] = f;
b->ngu_canh[b->so] = ngu_canh;
++b->so;
return 0;
}
void bus_phat(Bus *b, const SuKien *sk) {
for (size_t i = 0; i < b->so; ++i)
b->ham[i](sk, b->ngu_canh[i]);
}
static const TrangThai BANG[TT_SO_LUONG][SK_SO_LUONG] = {
/* SK_BAT SK_TAT */
[TT_CHO] = { TT_CHAY, TT_CHO },
[TT_CHAY] = { TT_CHAY, TT_DUNG },
[TT_DUNG] = { TT_CHAY, TT_DUNG },
};
TrangThai mtt_chuyen(TrangThai tt, TinHieu th) {
if (tt >= TT_SO_LUONG || th >= SK_SO_LUONG) return tt;
return BANG[tt][th];
}
const char *mtt_ten(TrangThai tt) {
static const char *const TEN[TT_SO_LUONG] = { "CHO", "CHAY", "DUNG" };
return tt < TT_SO_LUONG ? TEN[tt] : "?";
}#Module mmio
#ifndef MMIO_H
#define MMIO_H
#include <stdint.h>
/* Khoi thanh ghi gia lap, chay duoc tren may ban.
Tren phan cung that, doi dia chi cua KHOI0 thanh dia chi that. */
typedef struct {
volatile uint32_t CR; /* dieu khien, doc ghi */
const volatile uint32_t SR; /* trang thai, CHI DOC */
volatile uint32_t DR; /* du lieu, doc ghi */
volatile uint32_t BSRR; /* dat va xoa bit, chi ghi */
} Khoi;
enum {
CR_BAT = 1u << 0,
CR_NGAT = 1u << 1,
SR_SAN_SANG = 1u << 7,
};
extern Khoi *const KHOI0;
void mmio_bat(Khoi *k);
void mmio_tat(Khoi *k);
int mmio_san_sang(const Khoi *k);
void mmio_ghi(Khoi *k, uint32_t x);
void mmio_dat_bit(Khoi *k, unsigned n);
void mmio_xoa_bit(Khoi *k, unsigned n);
/* Chi co trong ban gia lap. */
void mmio_gia_lap_san_sang(int co);
uint32_t mmio_gia_lap_bsrr(void);
#endif#include "mmio.h"
/* Tren phan cung that, KHOI0 tro thang vao dia chi thanh ghi.
Tren may ban, no tro vao mot khoi bo nho gia lap.
Union cho phep phan "phan cung gia lap" ghi vao SR qua tho[],
con phan chuong trinh chi doc duoc SR qua k.SR. Khong can
ep bo const o dau ca. */
static union {
Khoi k;
volatile uint32_t tho[4];
} bo_nho;
Khoi *const KHOI0 = &bo_nho.k;
void mmio_bat(Khoi *k) { k->CR |= CR_BAT; }
void mmio_tat(Khoi *k) { k->CR &= ~(uint32_t)CR_BAT; }
int mmio_san_sang(const Khoi *k) { return (k->SR & SR_SAN_SANG) != 0; }
void mmio_ghi(Khoi *k, uint32_t x) {
while (!mmio_san_sang(k)) { }
k->DR = x;
}
/* BSRR: 16 bit thap dat, 16 bit cao xoa.
Mot lan GHI, khong doc, nen ngat khong chen vao giua duoc. */
void mmio_dat_bit(Khoi *k, unsigned n) { k->BSRR = 1u << n; }
void mmio_xoa_bit(Khoi *k, unsigned n) { k->BSRR = 1u << (n + 16); }
/* Chi co trong ban gia lap: dong vai phan cung. */
void mmio_gia_lap_san_sang(int co) {
if (co) bo_nho.tho[1] |= SR_SAN_SANG;
else bo_nho.tho[1] &= ~(uint32_t)SR_SAN_SANG;
}
uint32_t mmio_gia_lap_bsrr(void) { return bo_nho.tho[3]; }#Makefile và chạy thử
CC = gcc
CFLAGS = -std=c11 -O2 -D__USE_MINGW_ANSI_STDIO=1 \
-Wall -Wextra -Wpedantic -Wshadow -Wconversion
NGUON = bitutils.c generic.c callback.c mmio.c
DOI_TUONG = $(NGUON:.c=.o)
all: thu
thu: $(DOI_TUONG) test.o main.o
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
kiem: thu
./thu
sach:
rm -f *.o thu thu.exe
.PHONY: all kiem sach
bitutils.o: bitutils.h
generic.o: generic.h
callback.o: callback.h
mmio.o: mmio.h
test.o: test.h
main.o: test.h bitutils.h generic.h callback.h mmio.hgcc -std=c11 -O2 -D__USE_MINGW_ANSI_STDIO=1 -Wall -Wextra -Wpedantic -Wshadow -Wconversion -c -o bitutils.o bitutils.c gcc ... -c -o generic.o generic.c gcc ... -c -o callback.o callback.c gcc ... -c -o mmio.o mmio.c gcc ... -c -o test.o test.c gcc ... -c -o main.o main.c gcc ... -o thu bitutils.o generic.o callback.o mmio.o test.o main.o ./thu [bitutils] [generic] [callback] [mmio] 171 kiem tra, 0 hong
Dựng bộ khung trước
Viết
test.h,test.c,Makefilevà mộtmain.cchỉ in ra dòng tổng kết. Chạy được rồi mới viết module đầu tiên.Mỗi module: header trước, kiểm thử sau, cài đặt cuối
Viết nguyên mẫu, viết các dòng
KIEMgọi vào nguyên mẫu đó, rồi mới viết thân hàm. Cách này buộc bạn thiết kế giao diện từ phía người dùng.Bật cảnh báo ngay từ đầu
Thêm
-Wall -Wextra -Wpedantic -Wshadow -WconversionvàoCFLAGStừ dòng mã đầu tiên. Bật muộn là phải sửa hàng chục chỗ cùng lúc.Chạy lại toàn bộ sau mỗi module
Một trăm bảy mươi bài kiểm tra chạy trong vài mili giây, nên không có lý do gì để chạy chọn lọc.
Kiểm tra rò rỉ bộ nhớ cuối cùng
Trên Linux chạy
valgrind ./thu, hoặc dịch với-fsanitize=address. Hai modulebitutilsvàgenericđều cấp phát nên đây không phải bước bỏ được.
#Danh sách nghiệm thu
Chương 29, con trỏ hàm
- Khai báo được
HamXuLyvà giải thích được vì sao cặp ngoặc quanh*là bắt buộc. - Giải thích được vì sao mọi hàm gọi lại đều cần tham số
void *đi kèm. - Viết được bảng điều phối hai chiều với khởi tạo có chỉ định.
- Biết vtable thủ công khác gì bảng điều phối phẳng.
Chương 30, lập trình tổng quát
- Cài được vector
void *đúng, kể cả xử lýreallocthất bại. - Cài được vector sinh bằng macro và nêu được ba điểm mạnh, ba điểm yếu so với bản
void *. - Giải thích được
VE_CHUvàoffsetofhoạt động ra sao. - Dùng đúng
qsortvàbsearchvới hàm so sánh trả về ba giá trị.
Chương 31, thao tác bit
- Viết được năm macro
BIT,BAT,TAT,DAO,COmà không cần tra cứu. - Kể được ba cạm bẫy: độ ưu tiên toán tử, dịch số có dấu, và dịch quá độ rộng kiểu.
- Dùng được
__builtin_popcount,__builtin_clz,__builtin_ctzvà biết cả ba đều không xác định với đầu vào 0. - Cài được bitset trên mảng
uint64_tvới phép chia và chia dư thay bằng>> 6và& 63.
Chương 32, từ khóa
- Đặt được
volatileđúng chỗ trong khai báo khối thanh ghi, tức ở từng trường chứ không ở con trỏ. - Biết dùng
const volatilecho thanh ghi chỉ đọc và giải thích được vì sao hai từ khóa đó không mâu thuẫn. - Không ép bỏ
constở bất kỳ đâu trong dự án. - Nêu được vì sao
volatilekhông thay được_Atomickhi có nhiều luồng.
Chất lượng chung
- Dịch sạch với
-Wall -Wextra -Wpedantic -Wshadow -Wconversion, không một cảnh báo nào. - Mọi header đều có bảo vệ đưa vào và tự đủ.
- Mọi hàm và biến không nằm trong header đều là
static, kiểm tra được bằngnm. - Mọi hàm cấp phát đều có bài kiểm tra cho đường thất bại.
valgrindhoặc AddressSanitizer báo không rò rỉ.make sach && make kiemchạy từ đầu và cho ra0 hong.
Tự làm thử
- Dựng đủ cây thư mục và chạy
make kiemcho ra0 hong. - Sửa một hằng số trong bài kiểm tra cho sai đi và xác nhận thông báo chỉ đúng dòng, và mã thoát là 1.
- Bỏ
constkhỏi trườngSRrồi thử ghi vào nó, xem trình biên dịch nói gì. - Bỏ
(uint32_t)trongmmio_tatvà chạy lại với-Wconversion. - Chạy
nm --defined-onlytrên cả bốn tệp đối tượng và kiểm tra không có ký hiệu chữ hoa nào ngoài dự kiến. - Thêm module
chuoivới ít nhất mười bài kiểm tra.
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
- Một khung kiểm thử dùng được chỉ cần ba macro, hai biến đếm và một mã thoát khác 0 khi có lỗi.
#dk,__FILE__và__LINE__biến thông báo lỗi thành thứ mở thẳng được trong trình soạn thảo.- Union hai khung nhìn cho phép giả lập phần cứng mà không phải ép bỏ
constở đâu cả. -Wconversionbắt được lỗi~CR_BATtạo ra số có dấu, một cạm bẫy thật của Bài 31.2.nm --defined-onlylà cách nhanh nhất để kiểm tra ranh giới module: chữ hoa là công khai, chữ thường là riêng tư.