一、先看代码

#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_putcputchar 只是一层薄薄的转发。


六、C 语言 I/O 的分层架构

理解这段代码的关键,是看清这张图:

┌─────────────────────────┐
│    putchar('A')         │  ← 你调用的接口
├─────────────────────────┤
│    do_putc(c, stdout)   │  ← 适配层(这段代码)
├─────────────────────────┤
│    uart_send / 控制台    │  ← 真正的硬件输出
└─────────────────────────┘
层级 职责 例子
应用层 提供统一接口 putcharprintfputs
适配层 桥接标准接口与底层实现 do_putcfputc
驱动层 操作具体硬件 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!

原因有两个:

  1. 兼容 EOFEOF 通常是 -1,如果参数是 char,无法区分字符 0xFF 和 EOF
  2. 整数提升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 的完整实现,和这段代码的思路一模一样。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐