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技术爱好者更好地利用这些工具,我们整理了一份详尽…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...
