【C语言:可变参数列表】

news2025/1/12 6:00:25

文章目录

  • 1.什么是可变参数列表
  • 2.可变参数列表的分析与使用
    • 2.1使用
    • 2.2分析原理
    • 2.3分析原码

在这里插入图片描述

1.什么是可变参数列表

对于一般的函数而言,参数列表都是固定的,而且各个参数之间用逗号进行分开。这种函数在调用的时候,必须严格按照参数列表中参数的个数进行传参,否则编译器就会报错。
如果现在我要求2个数中的最大值,那么就可以这样写:
在这里插入图片描述
现在我的需求变了,我要求5个数的最大值,那怎么写?
在这里插入图片描述

如果现在我要求6个数的最大值呢? 你还要将代码继续写下去吗,那也太麻烦了吧,我的需求变一点点,你的代码就得变。
当然,也可以先将数放在一个大的数组里面。但是现在不让你使用数组,那你该怎么办呢?----使用可变参数列表

此处的...就是可变参数列表,num表示传入参数的个数。
可变参数至少有一个明确的参数。…表示其它元素,其它元素可以有,也可以没有
在这里插入图片描述

那么如何使用可变参数列表呢?

2.可变参数列表的分析与使用

可变参数列表的使用需要借用四个宏。

  • va_list
  • va_start
  • va_arg
  • va_end

关于这四个宏的功能我们能后面会详细讲到。

2.1使用

在这里插入图片描述

注意事项:

  1. 可变参数必须从头到尾逐个访问,如果你一开始就想访问中间的元素,这是办不到的。
  2. 参数列表中至少有一个参数,如果一个都没有,则无法使用va_start
  3. 这几个宏是无法直接判断实际存在参数的个数的,必须给他传递参数个数
    那不对呀,我们使用的printf就是使用的可变参数,那我们没有给它传递明确的参数呀?
    其实我们给它传递参了参数的个数,我们的%d,%c等格式控制符就说明了我传递了几个参数。
  4. 这些宏无法判断每个参数的类型。

2.2分析原理

接下来,我们就开始分析一下它的底层原理是如何实现的:
在了解过函数栈帧的形成后,我们知道函数调用时是会进行参数传递的;而且参数在栈帧的形参过程中是从右向左入栈的。(函数栈帧的创建与销毁可看这里)
在汇编语言中,通过查看内存我们看见看到确实是这样的

在这里插入图片描述

此时我们我们的最后压入栈中的元素5,也就是num就在内存中的该位置:

在这里插入图片描述

此时我们先猜测一下,我的num就是我最后压入栈中的元素(在栈中的地址较小),那先前压入的元素,就在num上面。既然我能找到num元素,那我取出num的地址再加上一,不就指向先前压入的元素了;那我就能访问他们,再继续让指针移动,就可以将他们全部访问到。那到底是不是这样实现的呢?而且地址+1是加4/8个字节,那其它类型(char、short)是怎么办的呢?

下面我们就先来测试一下对于char类型,它是怎么做的:

在这里插入图片描述
在调试过程中给你,我们可以发现,char类型的数据在入栈是也是压入4个字节,为什么会这样呢?
movsx是什么汇编指令?我们以前都是用的mov
在这里插入图片描述
看到这我就明白了,char类型的数据会整型提升为整型,然后在压入栈中。
这样就可以实现,无论外部数据如何变化,该函数都可以让指针+4/8个字节找到数据了。

因此,通过汇编我们可以看到,在可变参数场景下:

  1. 实际传入的参数使char、short、float,在编译器编译的时候,会自动进行提升。
  2. 函数内部使用的时候,根据类型提取数据(更多的是通过int、double来进行)

2.3分析原码

  1. va_list

该类型,其实就是对char*的重命名,在此我们也就不赘述了。

在这里插入图片描述

  1. va_end

在这里插入图片描述
该宏的作用就是将我们的arg指针置为NULL了,避免了野指针。

  1. va_start

这里我们的编译器对它们进行了封装,而且该宏又调用了两个宏

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
将3个宏替换一下,就是下面的结果。

#define __crt_va_start_a(arg, num) ((void)(arg= (char*)(&(num)) + _INTSIZEOF(num)))

该宏是什么意思呢? 就是对num进行取地址,然后强转为char*指针,再+4,赋值给arg;最后将该指针强转为void类型。
这里为什么是+4呢?

  • 这里的+4其实是为了让数据在内存中4字节对齐(向上取整)
    这个_INTSIZEOF宏我们稍后再看。

为什么强转为void类型

  • 待…

在这里插入图片描述

执行va_start(arg, num),此时arg指针就指向了第一个元素

在这里插入图片描述

  1. va_arg

再执行int max = va_arg(arg, int);、
我们来看一下这个宏又是再干什么

#define __crt_va_arg(arg, int)     (*(int*)((arg += _INTSIZEOF(int)) - _INTSIZEOF(int)))

在这里插入图片描述
该宏先执行画红线的部分,即先将arg向下动,完成指向下一个元素的任务,然后再用arg减去刚才移动的距离,又回到刚才的位置(注意:arg没回来),最后通过强制转换,提取出符合类型大小的数据
在这里插入图片描述
该宏有两个功能:

  • arg指向下一个元素
  • 使arg回指,然后取出地址中的内容

一行代码就执行了两个作用,很巧妙。
在这里插入图片描述
然后代码通过循环num-1次就遍历了所有元素。

  1. 下面我们就来研究一下_INTSIZEOF宏是如何计算指针走

在这里插入图片描述
_INTSIZEOF(n)的意思就是:计算一个最小数x,满足x>=n && x % 4 == 0 其实就是一种4字节的对齐方式。
例如:

  • n是1,2,3,4,对n进行sizeof(int)最小整数被取整的问题 就是4。
  • n是5,6,7,8,对n进行sizeof(int)最小整数被取整的问题 就是8。

那为什么要这样做呢?

  • 因为我们的数据在入栈的时候,都是按照4/8字节对齐的方式存储的,既然存的时候是按照4字节对齐的方式存的,那你取的也要按照4字节对齐的方式取。

那该宏是怎么办到的呢?

既然是对齐到4的最小整数倍处,那么本质是:n对应的4的最小整数倍 = 4*m。对n=7来说,m就是2,4的最小整数倍(对齐数)就是8。

  • 如果n能整除4,那么m就是n/4
  • 如果n不能整除4,那么m就是n/4+1

上面两种情况如何合并为一种情况呢?

(n + sizeof(int) - 1/sizeof(int)  ---->(n + 4 - 1) / 4
  • 如果n能整除4,那么m就是 (n+4-1)/ 4 ---->(n+3)/4,此时+3就不起作用,就是n/4
  • 如果n不能整除4,那么n=最大整除4的部分+R(R为n%4), 1<=R<4。那么m就是 (n+4-1)/ 4 ---->(最大整除4的部分+R+3)/4,其中 4<=R+3 <7,那最后m就等于了n/4 + (R+3 ) / 4------>n/4+1

知道了一个数x是4的最小几倍,那求x对应的4的对齐数就是:

(n + sizeof(int) - 1/sizeof(int) * sizeof(int)  ---->((n+4-1)*4)/4
 ---          最小几倍          ---

现在和源码还不太一样,那我们写一个简洁版
设n+4-1 = w,那表达是就变为了( w / 4) * 4,而4就是2 * 2,那w/4不就相当于右移两位,w*4就相当于左移两位;先右移两位,在左移两位,最终的结果就是将最后两个比特位置为0了嘛!
需要这么麻烦嘛?
直接w & ~3就可以了呀
所以最终式子就变成了这样(n+4-1)& ~ (4-1),这不就跟源码一样了嘛
源码:

在这里插入图片描述

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

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

相关文章

云卷云舒:【实战篇】Redis迁移

1. 简介 Remote Dictionary Server(Redis)是一个由Salvatore Sanfilippo写的key-value存储系统&#xff0c;是一个开源的使用ANSIC语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 2. 迁移原理 redis-sh…

src refspec master does not match any

新项目推送至 Git 空仓库时抛出如下异常 src refspec master does not match any 初始化 init 都做了但反复尝试 git push -u origin master 均无果 后发现权限不够 .... 起初设置为开发者,后变更为了主程序员再次尝试 push 成功 .... 以上便是此次分享的全部内容&#xff0c;…

Pandas数据可视化

pandas库是Python数据分析的核心库 它不仅可以加载和转换数据&#xff0c;还可以做更多的事情&#xff1a;它还可以可视化 pandas绘图API简单易用&#xff0c;是pandas流行的重要原因之一 Pandas 单变量可视化 单变量可视化&#xff0c; 包括条形图、折线图、直方图、饼图等 …

记一次服务器被入侵的排查过程

起因 阿里云安全中心报告了告警信息&#xff0c;同时手机短信、邮件、电话也接收到了来自阿里云的风险通知&#xff0c;感觉这方面阿里云还是不错。 排查及解决过程 这条wget指令究竟是怎么被运行的 我无法定位到攻击人员是通过什么样的方式让我的java程序执行了wget这条指…

打造清晰的日志管理策略:如何在 NestJS 中集成 winston 高级日志系统

前言 在Web应用程序的开发过程中&#xff0c;日志管理是不可或缺的一部分。日志可以帮助我们了解应用程序的运行状态&#xff0c;监控系统行为&#xff0c;以及在出现问题时快速定位和解决问题。 对于使用NestJS框架的项目来说&#xff0c;集成一个高效、可扩展的日志系统尤为…

Java:IO流详解

文章目录 基础流1、IO概述1.1 什么是IO1.2 IO的分类1.3 顶级父类们 2、字节流2.1 一切皆为字节2.2 字节输出流 OutputStream2.3 FileOutputStream类2.3.1 构造方法2.3.2 写出字节数据2.3.3 数据追加续写2.3.4 写出换行 2.4 字节输入流 InputStream2.5 FileInputStream类2.5.1 构…

RFM会员价值度模型

模型基本原理 会员价值度用来评估用户的价值情况&#xff0c;是区分会员价值的重要模型和参考依据&#xff0c;也是衡量不同营销效果的关键指标。 价值度模型一般基于交易行为产生&#xff0c;衡量的是有实体转化价值的行为。常用的价值度模型是RFM RFM模型是根据会员 最近…

C#,简单选择排序算法(Simple Select Sort)的源代码与数据可视化

排序算法是编程的基础。 常见的四种排序算法是&#xff1a;简单选择排序、冒泡排序、插入排序和快速排序。其中的快速排序的优势明显&#xff0c;一般使用递归方式实现&#xff0c;但遇到数据量大的情况则无法适用。实际工程中一般使用“非递归”方式实现。本文搜集发布四种算法…

8 单链表---带表头节点

上节课所学的顺序表的缺点 顺序表的最大问题&#xff1a;插入和删除时需要移动大量元素 链式存储的定义 链式存储的逻辑结构 链表中的基本概念&#xff1a; 注意&#xff1a;表头节点并不属于数据元素 单链表图示&#xff1a; 把3个需要的结构体定义出来&#xff1a; typdef …

《网络是怎样连接的》2.3节图表(自用)

图4.1&#xff1a;TCP拆分数据与ACK号 图4.2&#xff1a;实际工作中ACK号与序号的交互过程 首先&#xff0c;客户端在连接时需要计算出与从客户端到服务器方向通信相关的序号初始值&#xff0c;并将这个值发送给服务器&#xff08;①&#xff09;。 接下来&#xff0c;服务器会…

MySQL学习笔记2: MySQL的前置知识

目录 1. MySQL是什么?2. 什么是客户端&#xff0c;什么是服务器&#xff1f;3. 服务器的特点4. 安装mysql5. mysql 客户端6. mysql 服务器7. mysql的本体8. MySQL 使用什么来存储数据&#xff1f;9. 数据库的多种含义10. MySQL 存储数据的组织方式 1. MySQL是什么? MySQL 是…

新手养布偶猫如何选择猫主食冻干?K9、sc、希喂三个品牌推荐!

布偶猫是食肉动物&#xff0c;但由于肠胃脆弱敏感&#xff0c;所以在饮食上需要特别关注哦&#xff01;为了给它们最好的呵护&#xff0c;现在有了主食冻干这种优质猫主食&#xff01;它不仅符合猫咪的天然饮食习惯&#xff0c;还用了新鲜生肉做原料呢&#xff01;营养满分不说…

如何设计企业级业务流程?学习华为的流程六级分类经验

业务流程管理&#xff08;BPM&#xff09;是一种系统化的方法&#xff0c;用于分析、设计、执行、监控和优化组织的业务流程&#xff0c;以实现预期的目标和价值。业务流程管理中&#xff0c;流程的分级方法有多种&#xff0c;常见的有以下几种&#xff1a; APQC的流程分级方法…

Agilent安捷伦E4407B频谱分析仪26.5GHz

E4407B是安捷伦ESA-E系列频谱分析仪&#xff0c;它是一款能够适应未来需要的中性能频谱分析仪解决方案。该系列在测量速度、动态范围、精度和功率分辨能力上&#xff0c;都为类似价位的产品建立了性能标准。其灵活的平台设计使得研发、制造和现场服务工程师能够自定义产品&…

(Python + Selenium4)Web自动化测试自学Day2之动手尝试

目录 文章声明⭐⭐⭐让我们开始今天的学习吧&#xff01;小试牛刀关于select标签关于弹窗只有一个点击按钮的弹窗需要确认的弹窗用户可以输入的弹窗 文章声明⭐⭐⭐ 该文章为我&#xff08;有编程语言基础&#xff0c;非编程小白&#xff09;的 Python Selenium4 Web自动化测试…

在 docker 容器中配置双网卡,解决通讯的问题

目录 1. 查看当前网络信息 2. 创建自定义网络桥 3. 创建双网卡模式 4. 删除默认网卡 已经创建好了的 Docker 容器&#xff0c;要修改它的IP比较麻烦&#xff0c;网上找了几种不同的方法&#xff0c;经过试验都没有成功&#xff0c;下面通过配置双网上来解决 IP 的问题。…

无心剑七绝《译无止境》

七绝译无止境 人生跌宕几春秋 苦辣酸甜永不休 只待通灵成妙译 神思曼舞醉琼楼 2024年1月6日 平水韵十一尤平韵 无心剑的这首《译无止境》以七言绝句的形式&#xff0c;表达了对翻译事业的热爱和追求。 首句“人生跌宕几春秋”&#xff0c;意味着人生的曲折变化&#xff0c…

IP代理测试:Ping测试如何做?

您在访问互联网时是否遇到过持续滞后或花费很长时间等待网站加载的情况&#xff1f;为了避免这种情况&#xff0c;您可以测试 ping 以查看连接速度。如果您使用代理&#xff0c;此 ping 测试还会显示代理服务器的响应速度。 ping 测试是一个很有价值的工具&#xff0c;可以帮助…

修改mariadb的默认存储路径

首先mariadb服务正常启动 安装mariadb&#xff1a; https://blog.csdn.net/qq_50247813/article/details/135402502?spm1001.2014.3001.5502 停止mariadb服务 systemctl stop mariadb创建新的数据存放目录 mkdir /data将mariadb的数据拷贝到/data目录下 mv /var/lib/mysq…

Python+Appium自动化测试的使用步骤

这篇文章主要介绍了PythonAppium实现自动化测试的使用步骤&#xff0c;文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值&#xff0c;需要的朋友们下面随着小编来一起学习学习吧 一、环境准备 1.脚本语言&#xff1a;Python3.x IDE&…