标准IO_格式化IO之printf函数

news2024/11/19 5:32:59

目录

1.可变参数原理

1.1 函数参数入栈原理

1.2 可变参数如何实现?

1.2.1 可变参数实现原理

1.2.2 固定参数有什么用?

1.2.3 va_start,va_arg,va_end如何使用?

2.printf函数实现原理

2.1 printf函数流程

2.2 printf函数格式解析原理

2.2.1 printf函数原型

2.2.2 printf格式解析

3.实现一个简易版printf函数


1.可变参数原理

1.1 函数参数入栈原理

 图 1-1 函数参数入栈原理

函数参数采用从右至左的方式入栈,最右的参数从栈顶入栈,以此类推,每个参数都会存储在栈中,形成一个函数参数列表。

可变参数能够实现的根本原因是程序知道如何解析存储在栈中的函数参数列表。

1.2 可变参数如何实现?

1.2.1 可变参数实现原理

可变参数函数原型由固定参数和可变参数组成,如:

int func(fmt,...);

  • fmt:固定参数。
  • ...:可变参数。

前面我们已经知道函数参数是如何存储在栈中,要实现可变参数,我们需要从栈中还原出可变参数的每一个参数,如何才能从栈中还原出可变参数呢?

这个我们需要用到系统提供的方法:va_start,va_arg,va_end。

typedef char * va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( (void)(ap = (va_list)&v + _INTSIZEOF(v)) )
#define va_arg(ap,t) (*(t *)(ap = ap + _INTSIZEOF(t), ap - _INTSIZEOF(t)))
#define va_end(ap) ( (void)(ap = (va_list)0)) 
  • va_list:va_list实际为char指针,用来记录参数的地址。
  • va_start:用于初始化一个va_list类型的变量。它接受两个参数,第一个参数是要初始化的va_list变量,第二个参数是变长参数函数中的最后一个固定参数(实际指向的是...前面的固定参数)。va_start会根据最后一个固定参数的地址来确定可变参数列表的起始位置。
  • va_arg:用于访问变长参数列表中的每个参数。它接受两个参数,第一个参数是要访问的va_list变量,第二个参数是要访问的参数的类型,va_arg会返回当前参数的值。
  • va_end:将va_list设置成NULL,防止va_list变成野指针。

宏#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))解析?

_INTSIZEOF(n)作用为计算传入参数n在内存中占用的字节数,以4字节对齐。 因为是以4字节对齐,不足4字节的部分依然占用4字节。

举个栗子:

char类型,_INTSIZEOF(char) = ((sizeof(char) + sizeof(int) - 1) & ~(sizeof(int) - 1)) = (1+4 -1) & ~(4 -1) = 0x00000004 & 0xfffffff3= 4; 

short类型,_INTSIZEOF(short) = ((sizeof(short) + sizeof(int) - 1) & ~(sizeof(int) - 1)) = (2+4 -1) & ~(4 -1) = 0x00000005 & 0xfffffff3= 4;

int类型,_INTSIZEOF(int) = ((sizeof(int) + sizeof(int) - 1) & ~(sizeof(int) - 1)) = (4+4 -1) & ~(4 -1) = 0x00000007 & 0xfffffff3= 4;

long long int类型,_INTSIZEOF(long long int) = ((sizeof(long long int) + sizeof(int) - 1) & ~(sizeof(int) - 1)) = (8+4 -1) & ~(4 -1) = 0x0000000b & 0xfffffff3= 8;

1.2.2 固定参数有什么用?

固定参数主要有一下两个作用:

(1)定位可变参数的地址。

固定参数是一个实际的参数,我们很容易知道固定参数的地址,因为固定参数和可变参数地址上是连续的,知道固定参数地址后,我们能够通过固定参数地址获取到每个可变参数地址。

(2)定义可变参数遍历规则。

固定参数提供一些关键信息用于遍历可变参数,通常我们用的比较多的有以下情况:

  • 情况1:固定参数是一个整型数,该整型数数值代表可变参数个数,我们知道可变参数个数后,就能遍历整个可变参数表,如果不知道可变参数个数,我们无法知道可变参数截止点。
  • 情况2:固定参数是一个字符串,我们根据字符串的规定去解析可变参数,printf函数使用的是这种情况。这种情况,固定参数更多的像是正则表达式,我们解析该正则表达式,就能知道可变参数表的实际样貌。

1.2.3 va_start,va_arg,va_end如何使用?

 图 1-2 vva_start,va_arg,va_end如何使用

a.使用va_list定义一个指针变量ap。

b.调用va_start(ap, 固定参数1),将va_list指针变量指向固定参数1后的第一个地址。

c.每次调用va_arg(ap, 参数类型),va_arg会将va_list指针变量指向下一个可变参数地址,同时返回当前可变参数的值。

d.调用va_end,将va_list指针变量清零,防止va_list指针变量变成野指针。

2.printf函数实现原理

2.1 printf函数流程

图 2-1 printf函数流程

printf函数称为格式化IO函数,printf函数和普通输出函数(如:fputs,puts等函数)的区别在于多了格式化的操作,格式化就是可变参数解析。

printf函数需要实现两个功能:

  • 根据固定参数format完成可变参数解析。
  • 解析生成的字符串通过系统调用写入内核并输出到屏幕。

2.2 printf函数格式解析原理

2.2.1 printf函数原型

#include <stdio.h>

int printf(const char *format, ...);

函数参数:

format:固定参数,格式化字符串,用于解析可变参数。

...:可变参数。

返回值:

成功:返回一个整型值,表示成功输出的字符数。

失败:返回一个负数。

2.2.2 printf格式解析

printf函数格式由一个个基本格式“%[标志][宽度][.精度][长度]类型”组成。

[]为可选项。

其他项为必选项。

 表 2-1 printf格式

 表 2-2 标志表

表 2-3 宽度表

表 2-4 精度表

表 2-5 长度表

表 2-6 类型表

printf通常还需用到个特殊的字符表(转义字符表),如下表:

 表 2-7 转义字符表 

3.实现一个简易版printf函数

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int my_printf(const char *format, ...) {
    va_list ap;
#define BUF_SIZE (1024)
#define STR_SIZE (64)
    char buf[BUF_SIZE] = {0};
    char c;
    char *pos = buf;
    va_start(ap, format);
    while((c = *format)) {
        if (c == '%') {
            format++;
            c = *format;
            switch(c) {
                case 'd':
                    {
                        int value = va_arg(ap, int);
                        char str[STR_SIZE] = {0};
                        sprintf(str, "%d", value);
                        memcpy(pos, str, strlen(str));
                        pos += strlen(str);
                        break;
                    }
                case 'u':
                    {
                        unsigned int value = va_arg(ap, unsigned int);
                        char str[STR_SIZE] = {0};
                        sprintf(str, "%u", value);
                        memcpy(pos, str, strlen(str));
                        pos += strlen(str);
                        break;
                    }
                case 'o':
                    {
                        unsigned int value = va_arg(ap, unsigned int);
                        char str[STR_SIZE] = {0};
                        sprintf(str, "%o", value);
                        memcpy(pos, str, strlen(str));
                        pos += strlen(str);
                        break;
                    }
                case 'x':
                    {
                        unsigned int value = va_arg(ap, unsigned int);
                        char str[STR_SIZE] = {0};
                        sprintf(str, "%x", value);
                        memcpy(pos, str, strlen(str));
                        pos += strlen(str);
                        break;
                    }
                case 'c':
                    {
                        char value = va_arg(ap, int);
                        memcpy(pos, &value, 1);
                        pos++;
                        break;
                    }
                    break;
                case 's':
                    {
                        char *value = va_arg(ap, char *);
                        memcpy(pos, value, strlen(value));
                        pos += strlen(value);
                        break;
                    }
                case 'p':
                    {
                        long long *value = va_arg(ap, long long *);
                        char str[STR_SIZE] = {0};
                        sprintf(str, "%p", value);
                        memcpy(pos, str, strlen(str));
                        pos += strlen(str);
                        break;
                    }
                case '%':
                    {
                        memcpy(pos, &c, 1);
                        pos++;
                        break;
                    }
                default:
                    {
                        printf("format error");
                        return -1;
                    }
            }
        } else {
            memcpy(pos, &c, 1);
            pos++;
        }
        format++;
    }
    write(2, buf, strlen(buf));
    return 0;
}

int main(int argc, char *argv[]) {
    int num = 0;
    my_printf("%d,%u,%o,%x,%c,%s,%p,%%\n", 1234, 1234, 1234, 1234, 'a', "myprintf", &num);
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

WebSocket协议解析

文章目录 概要一、WS原理1.1、帧格式 二、WS实战2.1、客户端发起协议升级请求2.2、服务端响应协议升级2.3、核心事件2.4、心跳保活 三、总结 概要 项目中的IM系统是基于WebSocket做的&#xff0c;所以这里聊一下。 说到WS&#xff0c;不得不提HTTP,HTTP是基于TCP&#xff0c;面…

Mycat分片函数详解

Mycat新一代Mysql分布式集群,大数据处理中间件,中国第一开源软件 Checkout项目 可以用eclipse的svn插件来进行项目检出,也可以用Tortoise SVN等工具检出,由于maven(M2)中的buildnumber-maven-plugin 中的SVNkit最高支持1.7的SVN仓库,因此当你用Tortoise SVN 1.8的工具或版…

聊聊原子弹之父:奥本海默

最近诺兰的电影奥本海默即将热映,其改编自Kai Bird和 Martin J. Sherwin的 2005 年Pulitzer Prize 获奖小说:“American Prometheus: The Triumph and Tragedy of J. Robert Oppenheimer”。这本小说作者研究奥本海默25年,才得以成形,可见奥神本人身上的故事曲折和传奇。 …

MP的开发流程-2

RESTful的实现等级 0级&#xff1a;传统的RPC&#xff0c;基于SOAP的WS&#xff0c;调用的服务名&#xff0c;参数放在HTTP协议的body里面&#xff0c;同时必须以POST方式提交&#xff0c;问题在于你必须清楚的知道所有服务&#xff0c;子服务&#xff0c;及其参数的信息&…

SpringBoot环境标识设置及nacos匹配配置

本地环境标识设置 本地父类maven配置 可以看到相关的分类&#xff0c;设置环境标识主要需要用到profiles; <profiles><profile><id>dev</id><properties><!-- 环境标识&#xff0c;需要与配置文件的名称相对应 --><profiles.active&…

AX7A200教程(8): HDMI输入和输出显示1080p视频

文章目录 本章节主要将hdmi输入的1080p视频通过ddr3缓存&#xff0c;然后通过hdmi输出口输出到显示屏上显示 一&#xff0c; 突发读写命令 设置读写突发长度为64 //parameter defineparameter WRITE_LENGTH 64;parameter READ_LENGTH 64;parameter IDLE 3d0; …

SSM面试题-Spring容器的启动流程

解答: 1. BeanDefinitionReader读取配置文件(xml yml properties),创建BeanDefinition(存储bean的定义信息) 2. 配置文件读取成功后&#xff0c;将相应的配置转换成 BeanDefinition 的对象实例保存在DefaultListableBeanFactory#beanDefinitionMap 中 3. 根据配置的 BeanFacto…

fastadmin采坑之固定表格某一列

// 初始化表格table.bootstrapTable({url: $.fn.bootstrapTable.defaults.extend.index_url,pk: id,sortName: id,fixedColumns: true,fixedRightNumber: 1,columns: [[{checkbox: true},{field: id, title: __(Id)},{field: proposal_title, title: __(Proposal_title), opera…

Modbus Poll 软件----下载和安装

Modbus Poll 下载 modbus tools 官网地址&#xff1a;https://www.modbustools.com/ 步骤1 点击进入官网&#xff0c;然后点击 DOWNLOAD&#xff0c;进入下载界面。 步骤2 在下载界面&#xff0c;点击 Download 64bit &#xff0c;下载 Modbus Poll。 步骤3 下载完成 Mo…

如何生成丰富的啸叫样本?

前段时间有个公众号的朋友问我如何生成丰富的啸叫类型&#xff0c;当时回答比较简单&#xff0c;只是把啸叫产生的条件说了一下&#xff0c;后来在写AI降噪的N种数据扩增方法时候也简单提了一下使用冲激响应(Impluse Respose, IR)和增益产生啸叫&#xff0c;今天我们把这个坑填…

RabbitMQ 教程 | 第3章 客户端开发向导

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

第1章 JavaScript简史

JavaScript的起源 JavaScript是Netscape公司与Sun公司合作开发的在JavaScript诞生之前游览器就是显示超文本文档的简单的软件&#xff0c;JavaScript为此增加了交互行为ECMAScript是JavaScript的标准化&#xff0c;本质上是同一个语言JavaScript是一门脚本语言通常只能运行在游…

VCS ICO - Intelligent Coverage Optimization

ico是vcs提供的用于优化覆盖率的feature&#xff1b;一般用户通过dist solver bofore等约束了变量的随机概率&#xff0c;而ico会在用户约束的基础上&#xff0c;做一些自动“修正”&#xff0c;以此来优化随机激励&#xff0c;提高随机多样性&#xff0c;加速覆盖率收敛&#…

【腾讯云 Cloud Studio 实战训练营】通过云IDE构建Web3项目

文章目录 背景一、 前言二、 Cloud Studio 主要功能三、Cloud Studio 实验前期准备3.1. 注册平台 四、构建Web3项目项目中技术栈 五、其他功能演示六、常见问题及注意事项七、总结八、相关链接 ​ Cloud Studio 是基于浏览器的集成式开发环境&#xff08;IDE&#xff09;&#…

《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(12)-Fiddler设置IOS手机抓包,你知多少???

1.简介 Fiddler不但能截获各种浏览器发出的 HTTP 请求&#xff0c;也可以截获各种智能手机发出的HTTP/ HTTPS 请求。 Fiddler 能捕获Android 和 Windows Phone 等设备发出的 HTTP/HTTPS 请求。同理也可以截获iOS设备发出的请求&#xff0c;比如 iPhone、iPad 和 MacBook 等苹…

每日一题——只出现一次的数字

只出现一次的数字 题目链接 思路 要求为线性时间复杂度&#xff0c;即时间复杂度为O(n)&#xff0c;那么我们就不能用简单的两层循环来解决问题 要求只能使用常量额外空间&#xff0c;即空间复杂度为O(1)&#xff0c;那么我们就不能额外开辟一个数组来记录每个元素出现的次数…

Cpp学习——通过日期类来了解Cpp中的运算符重载

目录 一&#xff0c;日期类 二&#xff0c;运算符重载 运算符重载1(比较&#xff09; 1.< 2. 复用 3.> 4.! 5.< 6.> 运算符重载2(日期加减&#xff09; 0.准备条件------计算每月的日期函数 1. 2. 3.- 4.- 5.前置 6.后置 7前置-- 6.后置-- 7.计…

「BLIP 微调指南」以 Image-Text Captioning 任务为例

前言&#xff1a;近日需要用到 BLIP 微调下游任务&#xff0c;搜索发觉如今并无 BLIP 微调教程&#xff0c;下面就以 Image-Text Captioning 任务为例&#xff0c;演示如何完成 BLIP 模型在自己数据集上的微调。 目录 1. BLIP 介绍2. 关键代码定位3. 关键参数赋值4. 模型定义&a…

Scratch 教程 之 如何四舍五入保留一个小数到指定的数位

有些时候&#xff0c;我们需要四舍五入一个多位小数到指定的位&#xff0c;但scratch并没有这个积木&#xff0c;怎么做呢&#xff1f;我来教你&#xff5e; 我们创建一个函数&#xff0c;需要时调用就行了&#xff5e; 如图&#xff0c;创建一个带参函数&#xff0c;勾选"…

《GreenPlum系列-部署维护》GreenPlum数据库Standby故障处理

一、Standby故障 1.检查监控中心数据库状态 2.查看master节点数据库状态 su - gpadmin gpstate -f二、重启数据库 1.快速关闭数据库 [gpadminmdw pg_log]$ gpstop -M fast ... Continue with Greenplum instance shutdown Yy|Nn (defaultN): > y ...2.开启数据库 [gpad…