当前位置: 首页 > article >正文

C语言--深入printf

我们首先来看一个非常简单的例子

#include <stdio.h>

int main()
{
    printf("Hello World!\n");
    return 0;
}

这是每个初学者都会学习的一个输出语句,但我们这一次想要深挖一下它的内部。在这个说明一下,本人在这里使用的环境是msys2配置的mingw64,使用VScode查看代码,不同的环境结果可能不同。

首先我们进入stdio.h文件,并搜索printf
在这里插入图片描述
找到printf的实现
在这里插入图片描述

__mingw_ovr
__attribute__((__format__ (gnu_printf, 1, 2))) __MINGW_ATTRIB_NONNULL(1)
int printf (const char *__format, ...)
{
  int __retval;
  __builtin_va_list __local_argv; __builtin_va_start( __local_argv, __format );
  __retval = __mingw_vfprintf( stdout, __format, __local_argv );
  __builtin_va_end( __local_argv );
  return __retval;
}

一.__mingw_ovr

这是一个 MinGW 特定的宏,用于覆盖系统默认的 printf 实现。按住ctrl键点击可以跳转到宏的声明
在这里插入图片描述

#  define __mingw_ovr static \
      __attribute__ ((__unused__)) __inline__ __cdecl

也就是说,这个宏是一个用于在 MinGW 环境中对函数进行特定的修饰。
static:将函数或变量的作用域限制在当前文件内(即文件内部静态)。这意味着该函数只能在定义它的源文件中使用,不会被其他文件链接,不会向其他编译单元暴露接口。
attribute ((unused)):告诉编译器即使该函数或变量没有被使用,也不要发出警告。例如:

#include <stdio.h>
static void func(){}
int main()
{
    int a=0;
    return 0;
}

编译后会出现警告
在这里插入图片描述
如果加上这个

#include <stdio.h>
static __attribute__ ((__unused__)) void func(){}
int main()
{
    int a=0;
    return 0;
}

再编译一遍,这个没有用到的静态函数就没有警告了
在这里插入图片描述
__inline__:建议编译器将该函数内联展开,而不是通过常规的函数调用机制调用。内联函数可以减少函数调用的开销,提高性能。
例如:我们编写一个使用__inline__的版本

// tes_inline.c
#include <stdio.h>
static __inline__ void func()
{
    printf("hello world\n");
}
int main()
{
    int a=0;
    func();
    return 0;
}

使用gcc编译为汇编代码

gcc -S tes_inline.c -o tes_inline.s  

再写一个对照方案

#include <stdio.h>
static void func()
{
    printf("hello world\n");
}
int main()
{
    int a=0;
    func();
    return 0;
}
gcc -S tes_noline.c -o tes_noline.s

查看两个.s文件,两者是一样的,原因是__inline__知识对编译器的一个建议,编译器根据不同的优化方案可以不遵守
在这里插入图片描述
我们做一个调整,在有__inline__的文件里加入__attribute__((always_inline))表示更加强烈的意愿

#include <stdio.h>
static __inline__  __attribute__((always_inline)) void func()
{
    printf("hello world\n");
}
int main()
{
    int a=0;
    func();
    return 0;
}

再编译一遍查看结果

我们会发现内联版本里面没有func函数的部分
__cdecl:指定函数使用 C 调用约定(C calling convention)。这是最常见的调用约定,参数从右到左压栈,由调用者清理栈。
总之,__mingw_ovr 宏的定义综合了多个属性,目的是为了:
将函数限制在文件内部使用(static)。
避免未使用函数的编译警告(__attribute__ ((__unused__)))。
提高性能,建议编译器内联该函数(__inline__)。
确保函数使用标准的 C 调用约定(__cdecl)。

二.__attribute__((__format__ (gnu_printf, 1, 2)))

这个属性告诉编译器,printf 函数的第一个参数(索引为1)是一个格式字符串,并且后续的变参(从索引2开始)是根据该格式字符串解析的参数。这有助于编译器进行格式检查,避免格式化字符串错误。

三.__MINGW_ATTRIB_NONNULL(1)

ctrl点击一下,找到声明它的地方
在这里插入图片描述
也就是说__MINGW_ATTRIB_NONNULL(1)实际上就是__attribute__ ((__nonnull__ (1)))
确保第一个参数(即格式字符串)不能为空,否则会触发编译警告或错误。

四.__builtin_va_list

使用__builtin_va_list定义一个变长参数列表的局部变量__local_arg。这个__builtin_va_list的具体实现应该是内嵌在gcc中的,它的具体实现没有找到,不过有一种实现可能是这样的

typedef struct {
    unsigned int gp_offset;           // 通用寄存器偏移量
    unsigned int fp_offset;           // 浮点寄存器偏移量
    void *overflow_arg_area;          // 溢出参数区指针
    void *reg_save_area;              // 寄存器保存区指针
} __va_list_tag;

typedef __va_list_tag __builtin_va_list[1];

五.__builtin_va_start

__builtin_va_start( __local_argv, __format );传递一个变长参数列表,还有一个是最后一个固定变量,即变长参数之前的一个参数,也就是int printf (const char *__format, …)中省略号之前的那个__format。这个函数的目的是初始化变长参数列表,从 __format 参数之后开始。

六.__retval = __mingw_vfprintf( stdout,__format,__local_argv );

我们可以发现其声明在这里
在这里插入图片描述
其中FILE结构体的实现在这里
在这里插入图片描述

#ifndef _FILE_DEFINED
  struct _iobuf {
#ifdef _UCRT
    void *_Placeholder;
#else
    char *_ptr;       // 当前缓冲区指针
    int _cnt;         // 缓冲区中剩余的字符数
    char *_base;      // 缓冲区基地址
    int _flag;        // 文件状态标志
    int _file;        // 文件描述符
    int _charbuf;     // 用于未缓冲字符的存储
    int _bufsiz;      // 缓冲区大小
    char *_tmpfname;  // 临时文件名(如果文件是临时文件)
#endif
  };
  typedef struct _iobuf FILE;
#define _FILE_DEFINED
#endif

FILE 类型的主要作用是提供对文件流的抽象,使得文件操作更加方便和高效。

__retval = __mingw_vfprintf( stdout,__format,__local_argv );

查找stdout,发现
在这里插入图片描述
发现stdout实际上调用的__acrt_iob_func函数,该函数的声明在这里
在这里插入图片描述
我们可以通过从Sourceforge上下载mingw64的源码来看实现
在这里插入图片描述
这个__acrt_iob_func函数实际上是再次调用的__iob_func_函数,而这个函数经过查找发现出现在好多动态链接库里面没有公开
在这里插入图片描述
但总体而言,__acrt_iob_func这个函数就是用来获取stdin,stdout和stderr这三个流的。

__retval = __mingw_vfprintf( stdout,__format,__local_argv );

之后便是格式字符串和需要的参数。我们可以进入mingw64源码查看__mingw_vfprintf的实现
在这里插入图片描述
首先发现__mingw_vfprintf实际上是__vfprintf,我们再找__vfprintf
在这里插入图片描述
过程为
1.锁定文件流,防止并发问题。
2.调用__pformat进行实际的格式化输出操作。
3.解锁文件流。
4.返回格式化操作的结果。
至于__pformat的实现比较复杂,如果感兴趣可以自行探索。

总而言之,__retval = __mingw_vfprintf( stdout,__format,__local_argv );这个语句返回了成功输入到stdout的字符数量。

七.__builtin_va_end( __local_argv );

使用__builtin_va_end清理变长参数列表。这个的实现可能在gcc里面,mingw64的源码中没有,感兴趣的可以自行探索。
在这里插入图片描述

八.总结

总而言之,printf函数的源码流程为
1.初始化:定义返回值变量__retval和变长参数列表__local_argv。
2.处理变长参数:使用__builtin_va_start初始化变长参数列表。
3.调用底层函数:调用__mingw_vfprintf进行实际的格式化输出,并将结果存储在__retval中。
4.清理:使用__builtin_va_end清理变长参数列表。
5.返回结果:返回格式化输出的结果。


http://www.kler.cn/a/511976.html

相关文章:

  • MySQL中日期和时间戳的转换:字符到DATE和TIMESTAMP的相互转换
  • springboot项目属性配置方式
  • 简述mysql 主从复制原理及其工作过程,配置一主两从并验证。
  • 【系统分享01】Python+Vue电影推荐系统
  • 云上贵州多彩宝荣获仓颉社区先锋应用奖 | 助力数字政务新突破
  • 1.7 ChatGPT:引领AI对话革命的致胜之道
  • 阿里云安装mikrotik7配置内网互通
  • 欧拉(Euler 22.03)安装安装proxysql
  • 解决Spring+Vue的跨域问题
  • 精选了几道MySQL的大厂面试题,被提问的几率很高!
  • JavaScript笔记APIs篇02——DOM事件
  • 【Maui】下拉框的实现,绑定键值对
  • 【HarmonyOS NAPI 深度探索12】创建你的第一个 HarmonyOS NAPI 模块
  • [计算机网络]一. 计算机网络概论第一部分
  • 04商品详情
  • react中,使用antd的Upload组件切片上传.zip文件及压缩包的下载
  • PyTorch使用教程(15)-常用开源数据集简介
  • 【STM32-学习笔记-15-】MAX7219点阵屏模块
  • Redis - General - 未授权访问漏洞(用户配置问题)
  • Quartus:开发使用及 Tips 总结
  • 适合快递驿站出库仪一体机的安卓主板
  • 如何在龙蜥 OS(AliOS)上安装极狐GitLab?
  • canvas snake game
  • 面向CTF的python_requests库的学习笔记
  • 二十项零信任相关的前沿和趋势性技术-Extranet as a Service
  • 中国综合算力指数(2024年)报告汇总PDF洞察(附原数据表)