C语言深入解析 printf的底层源码实现
深入解析 printf
的底层源码实现
printf
是 C 标准库中最常用的函数之一,用于格式化输出字符串。它的底层实现复杂且高效,包含多个模块化的函数和机制。本文结合 GNU C Library(glibc)的源码,详细分析 printf
的实现原理,帮助读者理解其内部工作机制。
1. 概述
printf
的核心实现围绕以下几个组件展开:
__printf
:作为printf
的核心入口,负责接收参数并调用底层实现。__vfprintf_internal
:核心的格式化和输出逻辑。- 流操作(
FILE
结构):管理输出目标(如stdout
)和线程安全。 - 辅助宏与函数:如
va_list
处理可变参数,_IO_flockfile
进行流加锁。
2. 源码分析
2.1 __printf
函数
__printf
是 printf
的实际实现,代码如下:来源:https://github.com/bminor/glibc/blob/master/stdio-common/printf.c
#include <libioP.h>
#include <stdarg.h>
#include <stdio.h>#undef printfint
__printf (const char *format, ...)
{va_list arg;int done;va_start (arg, format); // 初始化可变参数列表done = __vfprintf_internal (stdout, format, arg, 0); // 调用底层格式化输出函数va_end (arg); // 清理可变参数列表return done; // 返回输出字符的总数
}#undef _IO_printf
ldbl_strong_alias (__printf, printf);
ldbl_strong_alias (__printf, _IO_printf);
关键点:
- 参数处理:
- 使用
va_list
处理可变参数。 va_start
初始化参数列表,va_end
确保资源清理。
- 使用
- 调用核心实现:
__vfprintf_internal
是真正执行格式化和输出的函数。- 参数中
stdout
指定输出目标为标准输出。
2.2 __vfprintf_internal
的实现
__vfprintf_internal
是底层的格式化输出核心函数。在 GNU glibc 中,它被定义为 vfprintf
的别名。以下是 vfprintf
的部分源码:来源:https://codebrowser.dev/glibc/glibc/stdio-common/vfprintf-internal.c.html#1520
int
vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
{/* 检查流方向 */
#ifdef ORIENTORIENT;
#endif/* 检查参数有效性 */ARGCHECK (s, format);#ifdef ORIENTif (_IO_vtable_offset (s) == 0&& _IO_fwide (s, sizeof (CHAR_T) == 1 ? -1 : 1)!= (sizeof (CHAR_T) == 1 ? -1 : 1))return EOF; // 流方向不匹配
#endifif (!_IO_need_lock (s)){struct Xprintf (buffer_to_file) wrap;Xprintf (buffer_to_file_init) (&wrap, s);Xprintf_buffer (&wrap.base, format, ap, mode_flags); // 核心解析printf的参数return Xprintf (buffer_to_file_done) (&wrap);}int done;_IO_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);_IO_flockfile (s); // 加锁以确保线程安全struct Xprintf (buffer_to_file) wrap;Xprintf (buffer_to_file_init) (&wrap, s);Xprintf_buffer (&wrap.base, format, ap, mode_flags);done = Xprintf (buffer_to_file_done) (&wrap);_IO_funlockfile (s); // 解锁_IO_cleanup_region_end (0);return done;
}
核心流程:
-
参数校验:
- 使用
ARGCHECK
和_IO_fwide
确保流方向正确,避免不匹配的写入。
- 使用
-
线程安全:
- 调用
_IO_flockfile
对流加锁,确保多线程环境下不会发生数据竞争。 - 解锁操作通过
_IO_funlockfile
实现。
- 调用
-
核心格式化逻辑:
- 使用
Xprintf_buffer
对输入的format
和va_list
进行解析。 - 将解析后的数据写入流。
- 使用
-
流写入:
- 数据写入通过
Xprintf (buffer_to_file_done)
完成,确保所有缓冲区内容正确输出。
- 数据写入通过
3. 流操作与线程安全
FILE
是 C 标准库中用于管理 I/O 流的结构。printf
的底层实现中,通过 FILE
结构控制输出目标(如 stdout
、文件等)。为了保证多线程环境下的安全性,glibc 使用以下机制:
- 加锁与解锁:
_IO_flockfile
和_IO_funlockfile
对流进行加锁和解锁,避免并发冲突。
- 缓冲区管理:
Xprintf_buffer
负责将格式化数据存储到缓冲区,避免频繁的 I/O 操作,提升性能。
4. 格式化字符串的解析
vfprintf
的核心任务是解析格式化字符串 format
,并根据对应的占位符从 va_list
中提取参数。例如:
- 简单格式:
"%d"
表示整数,va_arg
提取int
参数。 - 复杂格式:如
"%10.2f"
表示带宽度和精度的浮点数。
解析逻辑包括:
- 遍历
format
,识别%
开头的占位符。 - 根据占位符的类型调用不同的处理函数。
- 将结果写入缓冲区或目标流。
5. 代码运行示例
以下是一个简单的示例:
#include <stdio.h>int main() {int a = 42;printf("The answer is %d\n", a);return 0;
}
执行流程:
- 编译器将
printf
转换为__printf
的调用。 __printf
初始化va_list
并调用__vfprintf_internal
。__vfprintf_internal
解析格式字符串并从va_list
中提取参数。- 将解析结果写入
stdout
。
6. 总结
printf
的底层实现充分体现了 C 标准库的设计精髓:
- 高效的可变参数处理:通过
va_list
提供灵活的参数传递机制。 - 模块化设计:将参数解析、格式化、流写入等功能分离,易于扩展和维护。
- 线程安全:通过流加锁机制,确保多线程环境下的正确性。
深入理解 printf
的源码,不仅能帮助我们掌握 C 语言的底层原理,还能为高效编程和库开发提供重要参考。
分析和模拟 vfprintf
的实现
这段代码是 printf
底层实现的核心部分,它负责格式化字符串,并将格式化后的结果输出到指定的 FILE
流(如 stdout
)。通过宏定义和函数调用,代码实现了灵活的缓冲区管理和线程安全的操作。以下是对代码的详细解析以及基于宏的模拟实现。
1. 宏定义的展开
Link: https://codebrowser.dev/glibc/glibc/stdio-common/printf_buffer-char.h.html#19
Link: https://codebrowser.dev/glibc/glibc/include/printf_buffer.h.html#281
首先,让我们回顾宏定义的结构:
#define Xprintf(n) __printf_##n
#define Xprintf_buffer Xprintf(buffer) // 展开为 __printf_buffer
#define Xprintf_buffer_done Xprintf(buffer_done) // 展开为 __printf_buffer_done
#define Xprintf_buffer_flush Xprintf(buffer_flush) // 展开为 __printf_buffer_flush
// 其他类似宏省略
通过这些宏,代码可以动态生成函数或变量名,从而实现灵活的函数调用和代码复用。例如:
Xprintf(buffer)
展开为__printf_buffer
,表示与缓冲区操作相关的核心函数。Xprintf(buffer_done)
展开为__printf_buffer_done
,表示缓冲区完成后的处理函数。
2. 代码解析
2.1 函数头
int vfprintf(FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
FILE *s
:输出目标流,例如stdout
。CHAR_T *format
:格式化字符串,例如"%d %s"
。va_list ap
:包含格式化参数的列表。mode_flags
:控制格式化输出行为的标志。
2.2 流方向和参数检查
#ifdef ORIENT
ORIENT;
#endifARGCHECK(s, format);
ORIENT
:通常用于确定流的方向(宽字符或窄字符)。ARGCHECK
:验证输入参数的有效性,确保流和格式化字符串均不为空。
2.3 线程安全与缓冲区操作
无锁处理
if (!_IO_need_lock(s)) {struct Xprintf(buffer_to_file) wrap; // 定义缓冲区结构体Xprintf(buffer_to_file_init)(&wrap, s); // 初始化缓冲区Xprintf_buffer(&wrap.base, format, ap, mode_flags); // 核心格式化逻辑return Xprintf(buffer_to_file_done)(&wrap); // 完成缓冲区输出
}
解析:
- 如果不需要加锁(单线程环境),直接操作缓冲区:
- 定义
wrap
结构体(如__printf_buffer_to_file
),用于管理缓冲区。 - 初始化缓冲区,通过
Xprintf(buffer_to_file_init)
将wrap
与流s
关联。 - 调用
Xprintf_buffer
解析格式化字符串并填充缓冲区。 - 最后通过
Xprintf(buffer_to_file_done)
将缓冲区内容写入流并释放资源。
- 定义
加锁处理
int done;
_IO_cleanup_region_start((void (*)(void *)) &_IO_funlockfile, s);
_IO_flockfile(s); // 加锁
struct Xprintf(buffer_to_file) wrap;
Xprintf(buffer_to_file_init)(&wrap, s);
Xprintf_buffer(&wrap.base, format, ap, mode_flags);
done = Xprintf(buffer_to_file_done)(&wrap);
_IO_funlockfile(s); // 解锁
_IO_cleanup_region_end(0);
return done;
解析:
- 加锁保护:
- 使用
_IO_flockfile
加锁,确保多线程环境下的流安全。 - 注册清理函数
_IO_funlockfile
,即使函数异常退出也能自动解锁。
- 使用
- 执行与无锁处理相同的缓冲区初始化、格式化和输出逻辑。
- 解锁并返回写入字符总数。
3. 模拟实现
为了更好地理解这段代码,可以通过模拟实现部分功能,简化宏定义和核心逻辑:
缓冲区管理
#include <stdio.h>
#include <stdarg.h>
#include <string.h>#define Xprintf(n) __printf_##n
#define Xprintf_buffer Xprintf(buffer)typedef struct {char buffer[1024]; // 缓冲区FILE *stream; // 输出目标
} __printf_buffer;void __printf_buffer_init(__printf_buffer *buf, FILE *stream) {buf->stream = stream;memset(buf->buffer, 0, sizeof(buf->buffer)); // 清空缓冲区
}void __printf_buffer_flush(__printf_buffer *buf) {fputs(buf->buffer, buf->stream); // 输出缓冲区内容到流memset(buf->buffer, 0, sizeof(buf->buffer)); // 清空缓冲区
}void __printf_buffer_append(__printf_buffer *buf, const char *str) {strncat(buf->buffer, str, sizeof(buf->buffer) - strlen(buf->buffer) - 1);if (strlen(buf->buffer) > 1000) { // 模拟缓冲区满时刷新__printf_buffer_flush(buf);}
}
核心格式化逻辑
int vfprintf_simulated(FILE *s, const char *format, va_list ap) {__printf_buffer buf;__printf_buffer_init(&buf, s); // 初始化缓冲区const char *p = format;char temp[100];while (*p) {if (*p == '%' && *(p + 1)) { // 检测格式化占位符p++;switch (*p) {case 'd': {int val = va_arg(ap, int);snprintf(temp, sizeof(temp), "%d", val);__printf_buffer_append(&buf, temp);break;}case 's': {char *str = va_arg(ap, char *);__printf_buffer_append(&buf, str);break;}default:__printf_buffer_append(&buf, "%");__printf_buffer_append(&buf, (char[]){*p, '\0'});break;}} else {__printf_buffer_append(&buf, (char[]){*p, '\0'});}p++;}__printf_buffer_flush(&buf); // 刷新缓冲区return 0; // 示例返回值
}
示例调用
int main() {vfprintf_simulated(stdout, "Hello, %s! Your score is %d.\n", (va_list){"World", 100});return 0;
}
输出:
Hello, World! Your score is 100.
4. 总结
通过分析和模拟实现,我们可以看到 vfprintf
的核心逻辑:
- 缓冲区管理:通过结构体管理输出,减少 I/O 操作,提升效率。
- 线程安全:加锁保护流,避免多线程竞争。
- 格式化处理:解析格式化字符串,动态生成输出。
这些设计体现了 GNU C Library 的模块化和高效性,同时为理解复杂的底层函数提供了良好的案例。
另外可以参考笔者的另一篇博客:《C Programming Language》第二版书中printf的最小实现详细解析
后记
2025年1月27日于山东日照。
相关文章:

C语言深入解析 printf的底层源码实现
深入解析 printf 的底层源码实现 printf 是 C 标准库中最常用的函数之一,用于格式化输出字符串。它的底层实现复杂且高效,包含多个模块化的函数和机制。本文结合 GNU C Library(glibc)的源码,详细分析 printf 的实现原…...

go 循环处理无限极数据
数据表结构: CREATE TABLE permission (id int(11) NOT NULL AUTO_INCREMENT COMMENT 权限ID,permission_name varchar(255) DEFAULT NULL COMMENT 权限名称,permission_url varchar(255) DEFAULT NULL COMMENT 权限路由,status tinyint(1) DEFAULT NULL COMMENT 权…...

C# Dynamic关键字
一、引言:开启动态编程之门 在 C# 的编程世界里,长久以来我们习惯了静态类型语言带来的严谨与稳定。在传统的 C# 编程中,变量的类型在编译时就已经确定,这就像是给每个变量贴上了一个固定的标签,在整个代码执行过程中…...

ReactNative react-devtools 夜神模拟器连调
目录 一、安装react-devtools 二、在package.json中配置启动项 三、联动 一、安装react-devtools yarn add react-devtools5.3.1 -D 这里选择5.3.1版本,因为高版本可能与夜神模拟器无法联动,导致部分功能无法正常使用。 二、在package.json中配置启…...

【教学类-89-02】20250128新年篇02——姓名藏头对联(星火讯飞+Python,五言对联,有横批)
背景需求: 过年了,我想用幼儿的名字写对联,但是我根本不会写,于是尝试让AI来写。 1.我班的孩子的名字都是2字和3字的 2.惊喜发现,AI它很快就能生成带名字的对联 但是观察发现,如果是二个名字的对联&#…...

装机爱好者的纯净工具箱
对于每一位电脑用户来说,新电脑到手后的第一件事通常是检测硬件性能。今天为大家介绍一款开源且无广告的硬件检测工具——入梦工具箱。 主要功能 硬件信息一目了然 打开入梦工具箱,首先看到的是硬件信息概览。这里不仅包含了内存、主板、显卡、硬盘等常…...

【新春不断更】数据结构与算法之美:二叉树
Hello大家好,我是但凡!很高兴我们又见面啦! 眨眼间已经到了2024年的最后一天,在这里我要首先感谢过去一年陪我奋斗的每一位伙伴,是你们给予我不断前行的动力。银蛇携福至,万象启新程。蛇年新春之际…...

网站结构优化:加速搜索引擎收录的关键
本文来自:百万收录网 原文链接:https://www.baiwanshoulu.com/9.html 网站结构优化对于加速搜索引擎收录至关重要。以下是一些关键策略,旨在通过优化网站结构来提高搜索引擎的抓取效率和收录速度: 一、合理规划网站架构 采用扁…...

Effective Objective-C 2.0 读书笔记—— objc_msgSend
Effective Objective-C 2.0 读书笔记—— objc_msgSend 文章目录 Effective Objective-C 2.0 读书笔记—— objc_msgSend引入——静态绑定和动态绑定OC之中动态绑定的实现方法签名方法列表 其他方法objc_msgSend_stretobjc_msgSend_fpretobjc_msgSendSuper 尾调用优化总结参考文…...

[MySQL]事务的隔离级别原理与底层实现
目录 1.为什么要有隔离性 2.事务的隔离级别 读未提交 读提交 可重复读 串行化 3.演示事务隔离级别的操作 查看与设置事务的隔离级别 演示读提交操作 演示可重复读操作 1.为什么要有隔离性 在真正的业务场景下,MySQL服务在同一时间一定会有大量的客户端进程…...

项目升级Sass版本或升级Element Plus版本遇到的问题
项目升级Sass版本或升级Element Plus版本遇到的问题 如果项目有需求需要用到高版本的Element Plus组件,则需要升级相对应的sass版本,Element 文档中有提示,2.8.5及以后得版本,sass最低支持的版本为1.79.0,所升级sass、…...

C++中,存储两个相同类型的数据,数据结构
在C中,存储两个相同类型的数据,可以使用多种数据结构。这里有几种常见且合适的选择: 简单的变量: 最直接的方式就是使用两个独立的变量。这种方法简单直观,但不够结构化。 cpp int a 5; int b 10; std::pair&#x…...

python实战(十五)——中文手写体数字图像CNN分类
一、任务背景 本次python实战,我们使用来自Kaggle的数据集《Chinese MNIST》进行CNN分类建模,不同于经典的MNIST数据集,我们这次使用的数据集是汉字手写体数字。除了常规的汉字“零”到“九”之外还多了“十”、“百”、“千”、“万”、“亿…...

[论文阅读] (37)CCS21 DeepAID:基于深度学习的异常检测(解释)
祝大家新春快乐,蛇年吉祥! 《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座,并分享给大家,希望您喜欢。由于作者的英文水平和学术能力不高,需要不断提升,所以还请大家批评指正࿰…...

Linux - 进程间通信(2)
目录 2、进程池 1)理解进程池 2)进程池的实现 整体框架: a. 加载任务 b. 先描述,再组织 I. 先描述 II. 再组织 c. 创建信道和子进程 d. 通过channel控制子进程 e. 回收管道和子进程 问题1: 解答1ÿ…...

Kafka 消费端反复 Rebalance: `Attempt to heartbeat failed since group is rebalancing`
文章目录 Kafka 消费端反复 Rebalance: Attempt to heartbeat failed since group is rebalancing1. Rebalance 过程概述2. 错误原因分析2.1 消费者组频繁加入或退出2.1.1 消费者故障导致频繁重启2.1.2. 消费者加入和退出导致的 Rebalance2.1.3 消费者心跳超时导致的 Rebalance…...

SpringBoot+Electron教务管理系统 附带详细运行指导视频
文章目录 一、项目演示二、项目介绍三、运行截图四、主要代码1.查询课程表代码2.保存学生信息代码3.用户登录代码 一、项目演示 项目演示地址: 视频地址 二、项目介绍 项目描述:这是一个基于SpringBootElectron框架开发的教务管理系统。首先ÿ…...

操作系统(Linux Kernel 0.11Linux Kernel 0.12)解读整理——内核初始化(main init)之控制台工作
前言 在 Linux 内核中,字符设备主要包括控制终端设备和串行终端设备,对这些设备的输入输出涉及控制台驱动程序,这包括键盘中断驱动程序 keyboard.S 和控制台显示驱动程序 console.c,还有终端驱动程序与上层程序之间的接口部分。 终端驱动程序…...

Autogen_core: Message and Communication
目录 完整代码代码解释1. 消息的数据类:2. 创建代理人(MyAgent):3. 创建和运行代理人的运行时环境:4. 根据发送者路由消息的代理(RoutedBySenderAgent):5. 创建和运行带路由的代理&a…...

ComfyUI工作流教程、软件使用、开发指导、模型下载
在人工智能和设计技术迅速发展的今天,AI赋能的工作流已成为创意设计与生产的重要工具。无论是图片处理、服装试穿,还是室内设计与3D建模,这些智能化的解决方案极大地提高了效率和创作质量。 为了帮助设计师、开发者以及AI技术爱好者更好地利用这些工具,我们整理了一份详尽…...

零基础Vue学习1——Vue学习前环境准备
目录 环境准备 创建Vue项目 项目目录说明 后续开发过程中常用命令 环境准备 安装开发工具:vscode、webstorm、idea都可以安装node:V22以上版本即可安装pnpm 不知道怎么安装的可以私信我教你方法 创建Vue项目 本地新建一个文件夹,之后在文件夹下打开…...

定西市建筑房屋轮廓数据shp格式gis无偏移坐标(字段有高度和楼层)内容测评
定西市建筑房屋轮廓数据是GIS(Geographic Information System,地理信息系统)领域的重要资源,用于城市规划、土地管理、环境保护等多个方面。这份2022年的数据集采用shp(Shapefile)格式,这是一种…...

汉语向编程指南
汉语向编程指南 一、引言王阳明代数与流形学习理论慢道缓行理性人类型指标系统为己之学与意气实体过程晏殊几何学半可分离相如矩阵与生成气质邻域镶嵌气度曲面细分生成气质邻域镶嵌气度曲面细分社会科学概论琴生生物机械科技工业研究所软凝聚态物理开发工具包琴生生物机械 报告…...

Writing an Efficient Vulkan Renderer
本文出自GPU Zen 2。 Vulkan 是一个新的显式跨平台图形 API。它引入了许多新概念,即使是经验丰富的图形程序员也可能不熟悉。Vulkan 的主要目标是性能——然而,获得良好的性能需要深入了解这些概念及其高效应用方法,以及特定驱动程序实现的实…...

AI常见的算法
人工智能(AI)中常见的算法分为多个领域,如机器学习、深度学习、强化学习、自然语言处理和计算机视觉等。以下是一些常见的算法及其用途: 1. 机器学习 (Machine Learning) 监督学习 (Supervised Learning) 线性回归 (Linear Regr…...

LibreChat
文章目录 一、关于 LibreChat✨特点 二、使用LibreChat🪶多合一AI对话 一、关于 LibreChat LibreChat 是增强的ChatGPT克隆:Features Agents, Anthropic, AWS, OpenAI, Assistants API, Azure, Groq, o1, GPT-4o, Mistral, OpenRouter, Vertex AI, Gemi…...

Spring Boot 日志:项目的“行车记录仪”
一、什么是Spring Boot日志 (一)日志引入 在正式介绍日志之前,我们先来看看上篇文章中(Spring Boot 配置文件)中的验证码功能的一个代码片段: 这是一段校验用户输入的验证码是否正确的后端代码,…...

Spring Boot 实现文件上传和下载
文章目录 Spring Boot 实现文件上传和下载一、引言二、文件上传1、配置Spring Boot项目2、创建文件上传控制器3、配置文件上传大小限制 三、文件下载1、创建文件下载控制器 四、使用示例1、文件上传2、文件下载 五、总结 Spring Boot 实现文件上传和下载 一、引言 在现代Web应…...

慕课:若鱼1919的视频课程:Java秒杀系统方案优化 高性能高并发实战,启动文档
代码: Javahhhh/miaosha191: 运行成功了慕课若鱼1919的视频课程:Java秒杀系统方案优化 高性能高并发实战https://github.com/Javahhhh/miaosha191 https://github.com/Javahhhh/miaosha191 miaosha项目启动文档 需安装的配置环境: VMwar…...

React第二十七章(Suspense)
Suspense Suspense 是一种异步渲染机制,其核心理念是在组件加载或数据获取过程中,先展示一个占位符(loading state),从而实现更自然流畅的用户界面更新体验。 应用场景 异步组件加载:通过代码分包实现组件…...