讲透 putchar 的底层实现:从标准库到自定义 I/O
一、先看代码
#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的完整实现,和这段代码的思路一模一样。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)