標準庫 printf/sprintf 依賴 FILE* 與 stdio.h。本文展示一個零依賴、純 C 實現的 my_printf/my_sprintf,核心邏輯移植自 Linux 內核 vsprintf,支持 %d %x %p %s %c %o %u 等主流格式,可直接嵌入無標準庫環境(裸機、內核、Bootloader)。
// 1. 格式解析主循環 -> 2. 數值轉字符串 -> 3. 寫入緩衝區 // 入口:my_printf(fmt, ...) / my_sprintf(buf, fmt, ...) // 核心:vsprintf(buf, fmt, va_list) // 工具:number() 處理各進制轉換、寬度、精度、標志
static char* number(char* str, long num, int base, int size, int precision, int type) {
static const char digits[] = "0123456789ABCDEF";
char tmp[66]; int i = 0; char sign = 0; char locase = (type & 32);
// 符號處理
if (type & 2) { // SIGN
if (num < 0) { sign = '-'; num = -num; size--; }
else if (type & 4) { sign = '+'; size--; } // PLUS
else if (type & 8) { sign = ' '; size--; } // SPACE
}
// 進制前綴
if (type & 64) { // SPECIAL
if (base == 16) size -= 2;
else if (base == 8) size--;
}
// 核心轉換:除基取余
if (num == 0) tmp[i++] = '0';
else while (num) tmp[i++] = digits[((unsigned long)num) % base | locase], num /= base;
// 精度補零
if (i > precision) precision = i;
size -= precision;
// 填充/對齊/符號/前綴/數字 輸出順序嚴格按標準
if (!(type & (1|16))) while (size-- > 0) *str++ = ' '; // 右對齊空格填充
if (sign) *str++ = sign;
if (type & 64) { if (base==8) *str++='0'; else if (base==16) {*str++='0'; *str++=('X'|locase);} }
if (!(type & 16)) while (size-- > 0) *str++ = (type&1)?'0':' '; // 零填充
while (i < precision--) *str++ = '0';
while (i--) *str++ = tmp[i];
while (size-- > 0) *str++ = ' '; // 左對齊填充
return str;
}
int vsprintf(char* buf, const char* fmt, va_list args) {
char* str = buf;
for (; *fmt; fmt++) {
if (*fmt != '%') { *str++ = *fmt; continue; }
// 解析標志 - + # 0 空格
int flags = 0;
do { fmt++;
switch(*fmt) {
case '-': flags |= 16; break; // LEFT
case '+': flags |= 4; break; // PLUS
case ' ': flags |= 8; break; // SPACE
case '#': flags |= 64; break; // SPECIAL
case '0': flags |= 1; break; // ZEROPAD
default: goto parse_width;
}
} while(1);
parse_width:
// 解析寬度、精度、長度修飾符、格式字符
// ... 完整代碼見附件,核心邏輯:
// 1. 寬度:數字或 * 從 va_arg 取
// 2. 精度:.數字 或 .*
// 3. 長度:h/l/L
// 4. 格式字符:d i u o x X p c s n %
// 5. 根據格式調用 va_arg 取值,傳入 number()
}
*str = '\0';
return str - buf;
}
int my_sprintf(char* buf, const char* fmt, ...) {
va_list args; va_start(args, fmt);
int i = vsprintf(buf, fmt, args);
va_end(args); return i;
}
int my_printf(const char* fmt, ...) {
char buf[1024];
va_list args; va_start(args, fmt);
int printed = vsprintf(buf, fmt, args);
va_end(args);
puts(buf); // 或 write(1, buf, printed)
return printed;
}
int main() {
my_printf("字符串: %s\n", "hello world");
my_printf("十進制: %d\n", 42);
my_printf("十六進制: %x %X\n", 255, 255);
my_printf("指針: %p\n", (void*)0x7FFFABCD);
my_printf("字符: %c\n", 'Z');
my_printf("八進制: %o\n", 63);
my_printf("無符號: %u\n", 4294967295U);
char buf[128];
my_sprintf(buf, "sprintf測試: %d %s", 123, "abc");
my_printf("buf: %s\n", buf);
return 0;
}
字符串: hello world 十進制: 42 十六進制: ff FF 指針: 0x7fffabcd 字符: Z 八進制: 77 無符號: 4294967295 buf: sprintf測試: 123 abc
| 場景 | 價值 |
|---|---|
| 裸機/單片機 | 無標準庫、Flash 極小、需極簡輸出 |
| OS 內核/Bootloader | 不能依賴用戶態 libc、需自帶格式化 |
| 嵌入式測試框架 | 統一輸出接口、支持重定向、可裁剪格式 |
| 安全審計環境 | 避免標準庫漏洞、完全可控代碼路徑 |
hh/h/l/ll/j/z/t 對應不同 va_arg 類型,必須嚴格匹配。va_arg 額外讀取 int 參數。printf_fp.c。這份「零依賴 printf 核心」是嵌入式、內核、安全領域開發者的寶貴資產。理解其格式解析狀態機 + 數值轉字符串除基取余 + 緩衝區寫入三大核心流程,即可按需裁剪、擴展、審計。