可变参数列表

demo 2:求任意多个数据中的最大值(至少一个),要求不能使用数组
因为目前参数个数不确定,那么函数编写的时候,参数个数也无法确定,换句话说,函数也就没法编写
不过,C提供了满足该场景的解决方案:可变参数列表

使用

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdarg.h>

int FindMax(int num, ...)
{
	va_list arg; //char*
	va_start(arg, num); //指向可变参数部分

	int max = va_arg(arg, int); //根据类型,获取可变参数列表中的第一个数据
	for (int i = 1; i < num; i++)
	{
		int cur = va_arg(arg, int);//获取并比较其他的
		if (max < cur)
		{
			max = cur;
		}
	}
	va_end(arg);
	return max;
}

int main()
{
	int max = FindMax(5, 11, 22, 33, 44, 55);
	printf("%d\n", max);
	return 0;
}

在这里插入图片描述
通过反汇编我们可以看到形参从右往左依次压栈
如果你是这种直接往函数里面写实参的形式,这和我们传入变量的汇编稍有不同,但是这不重要,知道区别就可以
在这里插入图片描述
重要的是我们关注的栈顶寄存器esp,esp 记录的是栈顶的地址,在此处打断点我们可以通过内存中esp寄存器看到内存中压入栈中的形参,这里改为十六进制方便观察
在这里插入图片描述
va_list,va_start之类都是宏,我们先说两个最简单的
第一个va_list 就是定义一个char* 类型的指针arg,va_end就是把指针arg置空,这两个很好理解

在这里插入图片描述
va_start(arg,num)这个宏的作用呢就是让arg这个char*指针指向可变参数部分

va_arg(arg,int)就是通过传入的类型来提取出每个形参,这里传入int,就以4字节提取,就把所有可变参数列表里面的整形都提取出来了

demo 3: 如果将参数改成char类型,求char类型变量中的最大值,代码会有问题吗?
结果并未受影响,可是,我们解析的时候,是按照va_arg(arg, int)来解析的,这是为什么?
movsx : 相当于进行整形提升,看起来你传入的是char一个字节,但实际上在可变参数这里它已经隐式的将char提升成了整形
也就是说 movsx eax,btye ptr [e] 就是把char 类型e变量整形提升放到eax中
所以你按照int来解析没问题
在这里插入图片描述

注意事项

可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是,如果你
想一开始就访问参数列表中间的参数,那是不行的。

参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用 va_start 。
如果一个命名参数都没有直接三个点,编译器都直接报错。

这些宏是无法直接判断实际存在参数的数量。
这些宏无法判断每个参数的是类型。
如果在 va_arg 中指定了错误的类型,那么其后果是不可预测的。

原理

那上面除了va_list ,va_end我能理解,中间那两个宏我理解不了,所以要理解就必须在进一步
必须要看看宏是如何定义的
va_list 就是一个char类型
在这里插入图片描述
va_start(arg, num)的宏定义呢是下面这一坨,翻译一下就是 arg = (char
) &num + 4 最终就让arg指向可变参数部分了,INTSIZEOF(n) 的意思就是4字节对齐,num本身就是4字节的int类型,所以这里就是4
在这里插入图片描述
上面呢那个内存图是以4字节显示的,也能看,但是其实按1字节更好说
arg = (char*) &num + 4
va_list把arg定义成了char类型,让&num也就是第一个参数5的地址强转成char类型 ,char类型+1就加1,i(nt指针+1就+4)。
va_list是char
类型,方便后续按照字节进行指针移动
就是下面这个图和上面那个内存图一个意思,我不是画了红色的轨迹嘛
最终就让arg指向可变参数部分了
在这里插入图片描述

va_arg具体干了什么?
显然他要能做到依次提取11,21,31,41,51这五个形参,当然都是4字节整形嘛,因为你就是按va_arg(arg,int) int来提取的。

在这里插入图片描述
这个宏也比较狠,它做了两件事
先看ap += _INTSIZEOF( t ) ,_INTSIZEOF( t ) 说人话就是 _INTSIZEOF( int ),int 按4字节对齐还是4
也就是说先让ap啊,也就是arg指针,它在va_start之后指向可变参数部分也就是图上的11,ap += _INTSIZEOF( t ) -> ap += 4就让arg指向了21,至此arg已然是往后移动到下一个可变参数了!
这还没完,+= 以后也有返回值就是+=以后的地址又减去了4,也就是说又减回到了11这个地址然后通过你传入的类型t进行强转解引用,
也就是上面宏中 * (t * )解析出了其中一个可变参数!
你传入的不是int嘛,那就按照Int类型强转再解引用
当然了,如果你提取时va_arg(arg,char),传入的是char类型肯定就不对了,因为人家默认给你整形提升了,你还按照char提取

总结一下就是:
1.把”当前元素“提取出来
2. arg指向下一个待访问元素

那他怎么知道要提取多少次呢?你不是传入了num吗,这样我们利用循环就可以依次解析出所有可变参数了
看着下面的图,记住循环五次,按照上面的逻辑脑中实验一下就会发现最后arg会跑到00EFF8AC提出最后一个参数51,至此完成!
在这里插入图片描述
我们可以想象一下printf中设置的%d , %c ,%f … 之类不就是在确定类型 和个数吗,当然printf的实现还是蛮复杂的!

至此只有一个问题就是INTSIZEOF(n)这个宏它到底做了什么?你说是4字节对齐(向上取整)那怎么办到的?
宏是与类型无关的,进入里面都是sizeof(n),sizeof只看类型,上面不就是传入了int num变量 和 一个 int类型吗,但这不重点要谈的,但你得知道

你往里面传入char or short这种<4字节的类型,出来的就是4,你传入5,6,出来的就是8就是这个意思
在这里插入图片描述
这个宏说人话就是 INTSIZEOF(n) 计算的结果一定是能够整除4的最小整数,而且能够向上4字节取整

为什么要有这个4字节对齐?
因为入栈时如果是短整型本身就是按4字节对齐方式开辟的(整形提升),人家按4字节写的,你现在提取时只能按照人家的规则提取数据

第一步理解:求4的倍数m

既然是4的最小整数倍取整,那么本质是: x = 4*m , m是倍数,对7来讲,m就是2,对齐结果就是8

而m具体是多少,取决于n是多少

如果n能整除4,那么m就是n/4
如果n不能整除4,那么m就是n/4+1
比如 m = 3/4+1 = 1 m = 6/4 + 1 = 2

上面是两种情况,如何合并成为一种写法呢?

常见做法是 ( 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,1<=r<4 那么m就是 (n+4-1)/4->(能整除4部分+r+3)/4,其中
4<=r+3<7 -> 能整除4部分/4 + (r+3)/4 -> n/4+1

为什么 r 的范围是1 <= r < 4 呢?为什么不能是5?
我画了个图尝试理解一下,把你的n按照4一块一块分,分不够4的只有1,2,3也就是 1<= r < 4
在这里插入图片描述
第二步理解:求最小4字节对齐数
已经求出满足条件最小是4的几倍问题,现在只需要再乘以4就是能够整除4的最小整数

也就是
在这里插入图片描述
我们可以看到除4再乘4,你以为可以消掉是把?
不行,你还非得按照括号优先级,先计算最小几倍,再乘以4

不信的话你带入一个2,按照消掉的逻辑算算结果对吗?
2+3=5 不对,必须按照优先级先算倍数(5/4)*4=4

这样就能求出来4字节对齐的数据了,其实上面的写法,在功能上,已经和源代码中的宏等价了

第三步理解:理解源代码中的宏
拿出简洁写法:((n+4-1)/4)* 4,设w=n+4-1, 那么表达式可以变化成为 (w/4) * 4,而4就是2^2,w/4,不就相当于右移两位吗?,再次 * 4不就相当左移两位吗?先右移两位,在左移两位,最终结果就是,最后2个比特位被清空为0!
需要这么费劲吗?
w & ~3 不香吗?
所以,简洁版:(n+4-1) & ~(4-1)
原码版:( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ),无需先 / , 在 *

关于这个w / 2 相当于右移一个比特位,你想想一个2,比特位不就是10,2除以2=1 ,比特位不就是01吗,就相当于右移1位位
乘以2就是反过来!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Qt 天气预报项目

参考引用 QT开发专题-天气预报 1. JSON 数据格式 1.1 什么是 JSON JSON (JavaScript Object Notation)&#xff0c;中文名 JS 对象表示法&#xff0c;因为它和 JS 中对象的写法很类似 通常说的 JSON&#xff0c;其实就是 JSON 字符串&#xff0c;本质上是一种特殊格式的字符串…

使用影刀指令+python实现简单的长文本乱序加密

本文意在利用影刀指令python代码&#xff0c;实现一种较为简单的长文本加密和解密&#xff0c;流程结构分为两步&#xff1a; 加密原理–是把字符转为列表&#xff0c;利用列表random模块中的shuffle函数做随机乱序。解密原理–是利用了列表的索引追踪&#xff0c;先前创建字典…

VSCODE+QEMU+WSL调试RISCV代码(SBI、kernel)

前言 最近在对RISC-V架构比较感兴趣&#xff0c;正好手头有《RISC-V体系结构编程与实践》的书籍&#xff0c;就打算跟随笨叔将这块的知识学习起来&#xff0c;最开始当然是需要搭建一个基础的实验平台&#xff0c;本来笨叔是贴心的提供了VMare的环境&#xff0c;奈何天生叛逆的…

Ubuntu部署jmeter与ant

为了整合接口自动化的持续集成工具&#xff0c;我将jmeter与ant都部署在了Jenkins容器中&#xff0c;并配置了build.xml 一、ubuntu部署jdk 1&#xff1a;先下载jdk-8u74-linux-x64.tar.gz&#xff0c;上传到服务器&#xff0c;这里上传文件用到了ubuntu 下的 lrzsz。 ubunt…

文件基础知识

计算机中的流&#xff1a;在C语言中将通过输入/输出设备&#xff08;键盘、内存、显示器、网络等&#xff09;之间的数据传输抽象表述为“流”。 1、文本流和二进制流 在文本流中输入输出的数据是一系列的字符&#xff0c;可以被修改在二进制流中输入输出数据是一系列字节&am…

C++初阶(十三)vector

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、vector的介绍二、vector的模拟实现1、模拟实现2、测试结果 一、vector的介绍 vector的文…

基于YOLOv5的人群计数系统设计系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介系统概述系统功能核心技术系统架构系统优势 二、功能三、系统四. 总结  总结 一项目简介 基于YOLOv5的人群计数系统设计是一个非常有趣且具有挑战性的项目…

html5各行各业官网模板源码下载(1)

文章目录 1.来源2.源码模板2.1 HTML5白色简洁设计师网站模板 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/134682321 html5各行各业官网模板源码下载&#xff0c;这个主题覆盖各行业的html官网模板&#xff0c;效果模…

软件设计师——法律法规(一)

&#x1f4d1;前言 本文主要是【法律法规】——软件设计师法律法规的题目&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日…

【LeetCode刷题】数组篇1

&#x1f387;数组简单题Part &#x1f308; 开启LeetCode刷题之旅 &#x1f308; 文章目录 &#x1f387;数组简单题Part&#x1f370;1.两数之和&#x1f451;思路分析1.暴力法2.哈希表法 &#x1f370;26.删除有序数组中的重复项&#x1f451;思路分析1.双指针2.利用vector…

微信小程序上传报错TypeError: Failed to fetch

上传之后报message&#xff1a;TypeError: Failed to fetch这个错误。 关掉项目 > 选择项目的ide界面右上有个齿轮设置 > 代理

【面试】css预处理器之sass(scss)

目录 为什么引入css预处理器 可读性 嵌套&#xff1a;关系明朗 选择器 属性 伪类‘’ 变量&#xff1a;语义明确 默认变量&#xff1a;美元符号 $ 变量名:值 !default 全局变量&#xff1a;:global { $global-x: } 变量插值&#xff1a;#{} map键值对&#xff1a;$…

函数保留凸性的一些运算,限制为一条线

凸优化在学术研究中非常重要&#xff0c;经常遇到的问题是证明凸性。常规证明凸性的方式是二阶导数的黑塞矩阵为半正定&#xff0c;或者在一维函数时二阶导数大于等于零。但很多时候的数学模型并不那么常规、容易求导的&#xff0c;若能够知道一些保留凸性的运算&#xff0c;将…

Zemax光学设计——单透镜设计

单透镜系统参数&#xff1a; 入瞳直径&#xff1a;20mm F/#&#xff08;F数&#xff09;&#xff1a;10 全视场&#xff1a;10 波长&#xff1a;587nm 材料&#xff1a;BK7 优化方向&#xff1a;最佳均方根光斑直径 设计步骤 一、单透镜系统参数 步骤一&#xff1a;入…

红黑树的插入

一.红黑树的特征 红黑树是二叉搜索树红黑树分为内部结点和外部结点,将空指针视为外部结点,其它结点视为内部结点根结点和外部结点都是黑色从根结点到外部结点的路径上不能有连续的红结点从根结点到外部结点的路径上黑结点的数目相同从根结点到外部结点的最长路径的长度不超过最…

Spring Framework远程代码执行漏洞 CVE-2022-22965 漏洞复现

Spring Framework远程代码执行漏洞 CVE-2022-22965 漏洞复现和相关利用工具 名称: Spring Framework 远程命令执行漏洞 描述: Spring core是Spring系列产品中用来负责发现、创建并处理bean之间的关系的一个工具包&#xff0c;是一个包含Spring框架基本的核心工具包&#xff0…

爬虫代理技术与构建本地代理池的实践

爬虫中代理的使用&#xff1a; 什么是代理 代理服务器 代理服务器的作用 就是用来转发请求和响应 在爬虫中为何需要使用代理&#xff1f; 隐藏真实IP地址&#xff1a;当进行爬取时&#xff0c;爬虫程序会发送大量的请求到目标网站。如果每个请求都使用相同的IP地址&#xff…

深入Python元编程:了解声明与初始化定制元类

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 简介 在Python中&#xff0c;元编程是指在运行时创建或定制类的编程。元类是Python中最强大的元编程工具之一&#xff0c;允许您控制类的创建过程。元类是类的类&#xff0c;它控制类的实例化&#xff0c;允许您…

【软件测试学习】—软件测试模型(二)

【软件测试学习】—软件测试模型&#xff08;二&#xff09; 我 | 在这里 &#x1f469;‍&#x1f9b0;&#x1f469;‍&#x1f9b0; 读书 | 长沙 ⭐计算机科学与技术 ⭐ 本科 【2024届】 &#x1f383;&#x1f383; 爱好 | 旅游、跑步、网易云、美食、摄影 &#x1f396;️…

修复 MyBatis 中空值引起的 SQL 语法错误

修复 MyBatis 中空值引起的 SQL 语法错误 背景 最近在查看别人的项目代码时&#xff0c;遇到了一个问题&#xff1a;数据库中的数据为空。在调试过程中&#xff0c;发现问题出现在调用 MyBatis 中的方法&#xff1a;listByIds(Collection<? extends Serializable> idL…