C语言深入解析 printf的底层源码实现

深入解析 printf 的底层源码实现

printf 是 C 标准库中最常用的函数之一,用于格式化输出字符串。它的底层实现复杂且高效,包含多个模块化的函数和机制。本文结合 GNU C Library(glibc)的源码,详细分析 printf 的实现原理,帮助读者理解其内部工作机制。


1. 概述

printf 的核心实现围绕以下几个组件展开:

  1. __printf:作为 printf 的核心入口,负责接收参数并调用底层实现。
  2. __vfprintf_internal:核心的格式化和输出逻辑。
  3. 流操作(FILE 结构):管理输出目标(如 stdout)和线程安全。
  4. 辅助宏与函数:如 va_list 处理可变参数,_IO_flockfile 进行流加锁。

2. 源码分析
2.1 __printf 函数

__printfprintf 的实际实现,代码如下:来源: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);

关键点

  1. 参数处理
    • 使用 va_list 处理可变参数。
    • va_start 初始化参数列表,va_end 确保资源清理。
  2. 调用核心实现
    • __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;
}

核心流程

  1. 参数校验

    • 使用 ARGCHECK_IO_fwide 确保流方向正确,避免不匹配的写入。
  2. 线程安全

    • 调用 _IO_flockfile 对流加锁,确保多线程环境下不会发生数据竞争。
    • 解锁操作通过 _IO_funlockfile 实现。
  3. 核心格式化逻辑

    • 使用 Xprintf_buffer 对输入的 formatva_list 进行解析。
    • 将解析后的数据写入流。
  4. 流写入

    • 数据写入通过 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" 表示带宽度和精度的浮点数。

解析逻辑包括:

  1. 遍历 format,识别 % 开头的占位符。
  2. 根据占位符的类型调用不同的处理函数。
  3. 将结果写入缓冲区或目标流。

5. 代码运行示例

以下是一个简单的示例:

#include <stdio.h>int main() {int a = 42;printf("The answer is %d\n", a);return 0;
}

执行流程

  1. 编译器将 printf 转换为 __printf 的调用。
  2. __printf 初始化 va_list 并调用 __vfprintf_internal
  3. __vfprintf_internal 解析格式字符串并从 va_list 中提取参数。
  4. 将解析结果写入 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); // 完成缓冲区输出
}

解析

  1. 如果不需要加锁(单线程环境),直接操作缓冲区:
    • 定义 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;

解析

  1. 加锁保护:
    • 使用 _IO_flockfile 加锁,确保多线程环境下的流安全。
    • 注册清理函数 _IO_funlockfile,即使函数异常退出也能自动解锁。
  2. 执行与无锁处理相同的缓冲区初始化、格式化和输出逻辑。
  3. 解锁并返回写入字符总数。

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 的核心逻辑:

  1. 缓冲区管理:通过结构体管理输出,减少 I/O 操作,提升效率。
  2. 线程安全:加锁保护流,避免多线程竞争。
  3. 格式化处理:解析格式化字符串,动态生成输出。

这些设计体现了 GNU C Library 的模块化和高效性,同时为理解复杂的底层函数提供了良好的案例。

另外可以参考笔者的另一篇博客:《C Programming Language》第二版书中printf的最小实现详细解析

后记

2025年1月27日于山东日照。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/8380.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python从0到100(八十五):神经网络-使用迁移学习完成猫狗分类

前言: 零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Python爬虫、Web开发、 计算机视觉、机器学习、神经网络以及人工智能…

(1)SpringBoot入门+彩蛋

SpringBoot 官网(中文)&#xff1a;Spring Boot 中文文档 Spring Boot是由Pivotal团队提供的一套开源框架&#xff0c;可以简化spring应用的创建及部署。它提供了丰富的Spring模块化支持&#xff0c;可以帮助开发者更轻松快捷地构建出企业级应用。Spring Boot通过自动配置功能…

C语言从入门到进阶

视频&#xff1a;https://www.bilibili.com/video/BV1Vm4y1r7jY?spm_id_from333.788.player.switch&vd_sourcec988f28ad9af37435316731758625407&p23 //枚举常量 enum Sex{MALE,FEMALE,SECRET };printf("%d\n", MALE);//0 printf("%d\n", FEMALE…

MacOS安装Docker battery-historian

文章目录 需求安装battery-historian实测配置国内源相关文章 需求 分析Android电池耗电情况、唤醒、doze状态等都要用battery-historian&#xff0c; 在 MacOS 上安装 battery-historian&#xff0c;可以使用 Docker 进行安装runcare/battery-historian:latest。装完不需要做任…

公式与函数的应用

一 相邻表格相乘 1 也可以复制 打印标题

DeepSeek学术写作测评第二弹:数据分析、图表解读,效果怎么样?

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 针对最近全球热议的DeepSeek开源大模型&#xff0c;娜姐昨天分析了关于论文润色、中译英的详细效果测评&#xff1a; DeepSeek学术写作测评第一弹&#xff1a;论文润色&#…

MongoDB平替数据库对比

背景 项目一直是与实时在线监测相关&#xff0c;特点数据量大&#xff0c;读写操作大&#xff0c;所以选用的是MongoDB。但按趋势来讲&#xff0c;需要有一款国产数据库可替代&#xff0c;实现信创要求。选型对比如下 1. IoTDB 这款是由清华大学主导的开源时序数据库&#x…

动手学深度学习-卷积神经网络-3填充和步幅

目录 填充 步幅 小结 在上一节的例子&#xff08;下图&#xff09; 中&#xff0c;输入的高度和宽度都为3&#xff0c;卷积核的高度和宽度都为2&#xff0c;生成的输出表征的维数为22。 正如我们在 上一节中所概括的那样&#xff0c;假设输入形状为nhnw&#xff0c;卷积核形…

简易CPU设计入门:控制总线的剩余信号(二)

项目代码下载 请大家首先准备好本项目所用的源代码。如果已经下载了&#xff0c;那就不用重复下载了。如果还没有下载&#xff0c;那么&#xff0c;请大家点击下方链接&#xff0c;来了解下载本项目的CPU源代码的方法。 CSDN文章&#xff1a;下载本项目代码 上述链接为本项目…

【MySQL】 数据类型

欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;【MySQL】 数据类型 发布时间&#xff1a;2025.1.27 隶属专栏&#xff1a;MySQL 目录 数据类型分类数值类型tinyint类型数值越界测试结果说明 bit类型基本语法使用注意事项 小数类型float语法使用注意事项 decimal语…

深度剖析C++17中的std::optional:处理可能缺失值的利器

文章目录 一、基本概念与设计理念二、构建与初始化&#xff08;一&#xff09;默认构造&#xff08;二&#xff09;值初始化&#xff08;三&#xff09;使用std::make_optional&#xff08;四&#xff09;使用std::nullopt 三、访问值&#xff08;一&#xff09;value()&#x…

云计算架构学习之LNMP架构部署、架构拆分、负载均衡-会话保持

一.LNMP架构部署 1.1. LNMP服务搭建 1.磁盘信息 2.内存 3.负载信息 4.Nginx你们公司都用来干嘛 5.文件句柄(文件描述符 打开文件最大数量) 6.你处理过系统中的漏洞吗 SSH漏洞 7.你写过什么shell脚本 8.监控通过什么告警 zabbix 具体监控哪些内容 9.mysql redis查询 你好H…

[BSidesCF 2020]Had a bad day1

题目 这里有传参 文件包含使用伪协议读取flag 先读取index.php查看 /index.php?categoryphp://filter/readconvert.base64-encode/resourceindex 解码 index.php源码 <?php$file $_GET[category];if(isset($file)){if( strpos( $file, "woofers" ) ! false …

12 款开源OCR发 PDF 识别框架

2024 年 12 款开源文档解析框架的选型对比评测&#xff1a;PDF解析、OCR识别功能解读、应用场景分析及优缺点比较 这是该系列的第二篇文章&#xff0c;聚焦于智能文档处理&#xff08;特别是 PDF 解析&#xff09;。无论是在模型预训练的数据收集阶段&#xff0c;还是基于 RAG…

银行卡三要素验证接口:方便快捷地实现银行卡核验功能

银行卡三要素验证API&#xff1a;防止欺诈交易的有力武器 随着互联网的发展&#xff0c;电子支付方式也越来越普及。在支付过程中&#xff0c;银行卡是最常用的支付工具之一。然而&#xff0c;在一些支付场景中&#xff0c;需要对用户的银行卡信息进行验证&#xff0c;以确保支…

Lite.Ai.ToolKit - 一个轻量级的 C++ 工具包

&#x1f6e0;**Lite.Ai.ToolKit**&#xff1a;一个轻量级的 C 工具包&#xff0c;包含 100 个很棒的 AI 模型&#xff0c;例如对象检测、人脸检测、人脸识别、分割、遮罩等。请参阅 Model Zoo 和 ONNX Hub、MNN Hub、TNN Hub、NCNN Hub。 3700 Stars 711 Forks 0 Issues 6 贡献…

node.js 07.npm下包慢的问题与nrm的使用

一.npm下包慢 因为npm i 默认从npm官网服务器进行下包,但是npm官网服务器是海外服务器所以响应很慢. 于是我们通过npm下包的时候通常用淘宝镜像进行下包,下面是切换到淘宝镜像地址下包的操作. 二.nrm的使用 nrm是一个管理切换npm下包地址的工具,可以快速切换下包的地址. 安…

读书笔记--分布式服务架构对比及优势

本篇是在上一篇的基础上&#xff0c;主要对共享服务平台建设所依赖的分布式服务架构进行学习&#xff0c;主要记录和思考如下&#xff0c;供大家学习参考。随着企业各业务数字化转型工作的推进&#xff0c;之前在传统的单一系统&#xff08;或单体应用&#xff09;模式中&#…

基于ADS的电感和变压器的建模过程

1. 电感二端口建模 对于固定尺寸单圈电感&#xff0c;从0.5G-200GHz的仿真&#xff0c;并提取其模型 如果想要在50GHz前把模型建准&#xff0c;仿真可能要建到200G&#xff0c;因为需要高频的数据&#xff0c;频率越高信息也越多。首先要调用文件由于数据是存在一个文件夹里面的…

使用Maxscript定义纹理贴图的方法

在3ds Max中,MaxScript 是一种用于插件编写和自动化任务的强大工具。通过MaxScript,你可以创建和操作对象、材质、灯光等等。要为材质分配纹理贴图,你可以按照以下方法来编写脚本。直接代码: myBmp = bitmaptexture filename:"D:\map001.tga" meditmaterials[1]…