一、先看代码
#include <stdio.h> #include "putc.h" int putchar(int c) { return do_putc(c, stdout); }短短四行,却藏着 C 语言 I/O 体系的核心设计思想。
二、putchar到底是什么?
putchar是 C 标准库函数,声明在<stdio.h>中:
int putchar(int c);功能:向标准输出(stdout)写入一个字符。
返回值:写入成功返回写入的字符(c),失败返回EOF。
你每天写的putchar('A');,本质上就是在调用这个函数。
三、这段代码在干什么?
把标准库的putchar重定向到了一个自定义函数do_putc。
| 组成部分 | 作用 |
|---|---|
#include "putc.h" | 引入自定义头文件,声明do_putc函数 |
do_putc(c, stdout) | 核心:调用底层输出函数,把字符c写到stdout |
return | 透传返回值,保持与标准putchar一致的接口 |
一句话总结:这是一个适配层,把标准接口putchar绑定到了自定义的 I/O 实现上。
四、为什么不直接用fputc?
你可能会问:标准库里不是有fputc吗?
// glibc 中 putchar 的典型实现 int putchar(int c) { return fputc(c, stdout); }确实,大多数系统就是这么实现的。但在以下场景中,你必须自己写:
| 场景 | 原因 |
|---|---|
| 嵌入式/裸机开发 | 没有完整的 C 运行时,fputc不存在 |
| 自定义控制台 | 输出不是终端,而是串口、LCD、调试器 |
| Freestanding 环境 | C 标准允许不提供<stdio.h>的完整实现 |
| 教学/学习目的 | 理解 I/O 分层架构 |
所以这段代码常见于:
- 嵌入式 RTOS(如 FreeRTOS、RT-Thread)
- 操作系统内核早期引导
- 单片机裸机程序
五、do_putc可能长什么样?
putc.h里大概率声明了:
// putc.h int do_putc(int c, FILE *stream);而do_putc的实现可能是这样的(以串口输出为例):
// putc.c int do_putc(int c, FILE *stream) { if (c == '\n') { uart_send('\r'); // 换行前送回车 } uart_send(c); // 发送字符到串口 return c; }看到了吗?真正干活的是do_putc,putchar只是一层薄薄的转发。
六、C 语言 I/O 的分层架构
理解这段代码的关键,是看清这张图:
┌─────────────────────────┐ │ putchar('A') │ ← 你调用的接口 ├─────────────────────────┤ │ do_putc(c, stdout) │ ← 适配层(这段代码) ├─────────────────────────┤ │ uart_send / 控制台 │ ← 真正的硬件输出 └─────────────────────────┘| 层级 | 职责 | 例子 |
|---|---|---|
| 应用层 | 提供统一接口 | putchar、printf、puts |
| 适配层 | 桥接标准接口与底层实现 | do_putc、fputc |
| 驱动层 | 操作具体硬件 | UART 发送、LCD 写入 |
这就是 C 语言 I/O 的核心设计:接口与实现分离。
七、和putc/fputc的关系
| 函数 | 原型 | 区别 |
|---|---|---|
putchar(c) | int putchar(int c) | 只能写stdout,最简单 |
putc(c, stream) | int putc(int c, FILE *stream) | 可以指定输出流,是宏也是函数 |
fputc(c, stream) | int fputc(int c, FILE *stream) | 纯函数,无宏版本 |
do_putc(c, stream) | int do_putc(int c, FILE *stream) | 自定义,等价于fputc的角色 |
所以这段代码等价于:
int putchar(int c) { return fputc(c, stdout); // 标准写法 }只不过把fputc换成了自定义的do_putc。
八、关键细节:为什么参数是int而不是char?
int putchar(int c); // 不是 char!原因有两个:
- 兼容
EOF:EOF通常是-1,如果参数是char,无法区分字符0xFF和EOF - 整数提升:
char传参时会自动提升为int,直接用int更准确
所以调用时:
putchar('A'); // 正确,'A' 提升为 int putchar(EOF); // 也可以,虽然没意义九、总结
| 要点 | 内容 |
|---|---|
| 这段代码是什么 | putchar的自定义实现,转发给do_putc |
| 为什么这么写 | 没有标准库 / 需要自定义 I/O / 嵌入式场景 |
| 核心思想 | 接口与实现分离,putchar只是转发层 |
do_putc的角色 | 等价于fputc,是真正干活的函数 |
参数为什么是int | 兼容EOF+ 整数提升 |
记住一句话:putchar不生产字符,它只是字符的搬运工。真正把字符送出去的,是do_putc背后的那串驱动代码。
如果你在做嵌入式开发或者想深入理解 C 运行时,建议去读 glibc 的
libio/ioputs.c,里面有_IO_putc的完整实现,和这段代码的思路一模一样。