Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 32.430 phút đọc

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ả.

Cây thư mụ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
ModuleNội dungKhái niệm chính
callbackHàng đợi sự kiện, bảng điều phối, máy trạng tháiCon trỏ hàm, ngữ cảnh void *, bảng tra hằng
genericVector void *, vector sinh bằng macro, danh sách xâm nhậpvoid *, sinh mã, container_of, qsort
bitutilsThao tác bit, hàm dựng sẵn, bitset độngToán tử bit, popcount, ctz, mảng uint64_t
mmioKhố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

test.h
#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
test.c
#include "test.h"

int test_so_kiem = 0;
int test_so_hong = 0;

#Module bitutils

bitutils.h
#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
bitutils.c, phần thao tác bit
#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);
}
bitutils.c, phần bitset
#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

generic.h
#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
generic.c
#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

callback.h
#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
callback.c
#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

mmio.h
#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
mmio.c
#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ử

Makefile
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.h
terminal
mingw32-make kiem
gcc -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
  1. Dựng bộ khung trước

    Viết test.h, test.c, Makefile và một main.c chỉ in ra dòng tổng kết. Chạy được rồi mới viết module đầu tiên.

  2. 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 KIEM gọ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.

  3. Bật cảnh báo ngay từ đầu

    Thêm -Wall -Wextra -Wpedantic -Wshadow -Wconversion vào CFLAGS từ dòng mã đầu tiên. Bật muộn là phải sửa hàng chục chỗ cùng lúc.

  4. 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.

  5. 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 module bitutils và 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 HamXuLy và 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ý realloc thấ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_CHU và offsetof hoạt động ra sao.
  • Dùng đúng qsort và bsearch vớ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, CO mà 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_ctz và 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_t với phép chia và chia dư thay bằng >> 6 và & 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 volatile cho 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 volatile không thay được _Atomic khi 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ằng nm.
  • Mọi hàm cấp phát đều có bài kiểm tra cho đường thất bại.
  • valgrind hoặc AddressSanitizer báo không rò rỉ.
  • make sach && make kiem chạy từ đầu và cho ra 0 hong.

Tự làm thử

  1. Dựng đủ cây thư mục và chạy make kiem cho ra 0 hong.
  2. 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.
  3. Bỏ const khỏi trường SR rồi thử ghi vào nó, xem trình biên dịch nói gì.
  4. Bỏ (uint32_t) trong mmio_tat và chạy lại với -Wconversion.
  5. Chạy nm --defined-only trê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.
  6. Thêm module chuoi vớ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ả.
  • -Wconversion bắt được lỗi ~CR_BAT tạo ra số có dấu, một cạm bẫy thật của Bài 31.2.
  • nm --defined-only là 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ư.