`TSDK.H` 是一個針對 C 語言常見開發場景(字符串處理、文件操作、隨機數、時間計算、端序檢測、日志記錄)封裝的輕量級頭文件庫。代碼風格乾淨、註釋完善、無第三方依賴,適合嵌入式、Windows/Linux 跨平台項目作為基礎設施層。本文逐模塊解析其設計細節與使用場景。
TSDK.H
├── 編譯器適配層 (_Cdecl, DOS/非DOS SAY 宏)
├── 通用工具宏 (MAX, MIN, FATAL, VERSION, bool 類型別名)
├── 字符串處理 (skip_spaces, strim)
├── 隨機數生成 (t_random)
├── 文件操作 (copy_file, file_exist)
├── 日志記錄 (log_save)
├── 日期時間 (is_leap_year, which_day)
└── 端序檢測 (is_little_endian)
// 調用約定適配
#if __STDC__
# define _Cdecl
#else
# define _Cdecl cdecl
#endif
// 跨平台輸出宏
#ifndef DOS
# define SAY(x...) printf(x) // GCC 可變參宏
#else
# define SAY(x) printf(x) // 老式 MSVC 不支援可變參宏
#endif
// 安全極值宏 (帶括號防副作用)
#ifndef MAX
# define MAX(a,b) ((a)<(b)?(b):(a))
#endif
#ifndef MIN
# define MIN(a,b) ((a)<(b)?(a):(b))
#endif
// 致命錯誤退出宏 (do-while(0) 保證語句級語義)
#define FATAL(x) do { \
printf("\n[-] PROGRAM ABORT : %s", x); \
exit(1); \
} while(0)
// 類型別名
#define VERSION "1.0.0"
#define true 1
#define false 0
typedef char bool; // C89 無 _Bool,用 char 模擬
// 跳過前導空白 (isspace 檢查空格、\t、\n、\r、\v、\f)
char *skip_spaces(const char *str) {
while (isspace(*str)) ++str;
return (char *)str;
}
// 雙向修剪空白 (就地修改,返回原指針)
char *strim(char *s) {
s = skip_spaces(s); // 去首部
size_t size = strlen(s);
if (!size) return s;
char *end = s + size - 1;
while (end >= s && isspace(*end)) end--; // 找尾部非空白
*(end + 1) = '\0'; // 截斷
return s;
}
適用場景:配置文件解析、命令行參數清洗、網絡協議字段提取。**注意**:就地修改原緩衝區,調用前確保可寫。
// 返回 [a, b) 區間隨機整數 (含 a,不含 b)
int t_random(int a, int b) {
if (a >= b) return -1;
srand(time(NULL)); // 以當前時間播種
return (rand() % (b - a)) + a;
}
缺陷警示:
// 二進制文件拷貝 (逐字節 fgetc/fputc,簡單但低效)
bool copy_file(char *src, char *dst, int flag) {
if (flag == 0 && file_exist(dst)) return false; // flag=0 目標存在則不覆蓋
FILE *fin = fopen(src, "rb"), *fout = fopen(dst, "wb");
if (!fin || !fout) { if (fin) fclose(fin); if (fout) fclose(fout); return false; }
int ch;
while ((ch = fgetc(fin)) != EOF) fputc(ch, fout);
fclose(fin); fclose(fout);
return true;
}
// 文件存在性檢查 (access 系統調用)
bool file_exist(char *file) {
return !access(file, 0); // 存在返回 0,取反為 true
}
優化建議:大文件拷貝用 `fread`/`fwrite` 批量塊操作 (如 4KB/64KB),或平台特定 `sendfile`/`CopyFile` 系統調用避免用戶態拷貝。
bool log_save(char *log_file, char *err_msg) {
FILE *p = fopen(log_file, "wb"); // "wb" 會截斷文件!建議 "ab" 追加
if (!p) return false;
fprintf(p, "[Err]: %s\n", err_msg);
fclose(p);
return true;
}
關鍵 Bug**:`fopen(log_file, "wb")` 以**寫入模式截斷**文件,每次調用只保留最新一條日志。**必須改為 `"ab"` (二進制追加)** 保留歷史記錄。
// 閏年規則:400年整除 或 (4年整除 且 非100年整除)
bool is_leap_year(int year) {
return ((year % 100 == 0) && (year % 400 == 0)) ||
((year % 100 != 0) && (year % 4 == 0));
}
// 計算年內第幾天 (1-366)
int which_day(int year, int mon, int day) {
static int mdays[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30}; // 索引1起
int days = day;
for (int i = 1; i < mon; ++i) days += mdays[i]; // 累加前置月份天數
if (mon > 2 && is_leap_year(year)) ++days; // 閏年且過了2月,+1
return days;
}
優化點:`mdays` 可改為 `const`,循環起始 `i=1` 更符合月份直覺 (原碼 `i=0` 累加 `mdays[0]=0` 無傷大雅但不直觀)。
bool is_little_endian(void) {
union { int a; char b; } c;
c.a = 1; // 整數 1 在內存中:小端 01 00 00 00,大端 00 00 00 01
return (c.b == 1); // 讀取最低位字節:小端為 1,大端為 0
}
應用場景:網絡通信 (網絡序大端) 與主機序轉換、二進制文件格式解析、跨平台數據序列化。現代代碼優先用 `
#ifndef __TSDK_H__
#define __TSDK_H__
#include
#include
#include
#include // for exit, access
#include
// ... 編譯適配、基礎宏 (同上) ...
// 函數聲明
bool _Cdecl copy_file(char *src, char *dst, int flag);
bool _Cdecl file_exist(char *file);
char *_Cdecl skip_spaces(const char *str);
char *_Cdecl strim(char *p);
int _Cdecl t_random(int a, int b);
bool _Cdecl log_save(char *log_file, char *err_msg);
bool _Cdecl is_leap_year(int year);
int _Cdecl which_day(int year, int mon, int day);
bool _Cdecl is_little_endian(void);
// ... 函數定義 (建議移至 .c 文件,頭文件僅聲明) ...
#endif // __TSDK_H__
| 模塊 | 現狀 | 建議改進 |
|---|---|---|
| 隨機數 | 每次調用 srand | 分離 srand 初始化;提供 64位/浮點/非均勻分布 |
| 文件拷貝 | 逐字節 fgetc/fputc | 批量 fread/fwrite;異步 I/O;進度回調 |
| 日志 | "wb" 截斷模式 | **修為 "ab" 追加**;日志級別;輪轉;線程安全 |
| 字符串 | 就地修剪 | 提供非修改版 `strim_copy`;Unicode 感知 |
| 時間 | 基礎計算 | 時區感知;ISO8601 解析/格式化 |
| 架構 | 頭文件含定義 | **拆分 .h/.c**;隱藏內部符號;單元測試 |