可变参数列表

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就是反过来!

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

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

相关文章

《golang设计模式》第三部分·行为型模式-09-策略模式(Strategy)

文章目录 1. 概述1.1 作用1.1 角色1.2 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1. 概述 1.1 作用 策略&#xff08;Strategy&#xff09;是用于封装一组算法中单个算法的对象&#xff0c;这些策略可以相互替换&#xff0c;使得单个算法的变化不影响使用它的客户端。 1.1 …

掌握Flask:从入门到精通指南

掌握Flask&#xff1a;从入门到精通指南 Flask 是一个轻量级的 Python Web 应用程序框架&#xff0c;具有简单易学、灵活性高等特点&#xff0c;适合用于快速开发 Web 应用程序。本文将全面介绍 Flask 框架的各个方面&#xff0c;包括基本概念、路由、模板渲染、表单处理、数据…

LoadRunner自动化测试工具的应用

目录 第一部分:Loadrunner的简介 1.1 安装注意事项 1.2 协议的选择或者 VUSER 类型的选取 1.3 LR 的基本原理 1.4 测试脚本录制/分配所遵循的几个原则 第二部分:录制脚本 2.1 录制脚本前需要理解的几个基本概念 2.1.1 事务(Transaction) 2.1.2 集合点(Rendezvous) 2.1…

智能手表上的音频(四):语音通话

上篇讲了智能手表上音频文件播放。本篇开始讲语音通话。同音频播放一样有两种case&#xff1a;内置codec和BT。先看这两种case下audio data path&#xff0c;分别如下图&#xff1a; 内置codec下的语音通话audio data path 蓝牙下的语音通话audio data path 从上面两张图可以看…

Unity C++交互

一、设置Dll输出。 两种方式&#xff1a; 第一&#xff1a;直接创建动态链接库工程第二&#xff1a;创建的是可执行程序&#xff0c;在visual studio&#xff0c;右键项目->属性(由exe改成dll) 二、生成Dll 根据选项Release或Debug&#xff0c;运行完上面的生成解决方案后…

主播岗位面试

一、自我介绍 在面试的开始阶段&#xff0c;你需要准备一个简洁而有力的自我介绍。这个自我介绍应该包括你的姓名、教育背景、工作经验以及你为何对这个主播职位感兴趣。这个自我介绍应该控制在1-2分钟之内&#xff0c;避免冗长的表述。 二、主播经历和特点 在这个环节&…

一文学会Aiohttp

一、什么是aiohttp库 aiohttp库官网&#xff1a;https://docs.aiohttp.org/en/stable/ aiohttp是一个Python的HTTP客户端/服务器框架&#xff0c;它基于asyncio库实现异步编程模型&#xff0c;可以支持高性能和高并发的HTTP通信。aiohttp用于编写异步的Web服务器、Web应用程序…

36 - 电商系统表设计优化案例分析

如果在业务架构设计初期&#xff0c;表结构没有设计好&#xff0c;那么后期随着业务以及数据量的增多&#xff0c;系统就很容易出现瓶颈。如果表结构扩展性差&#xff0c;业务耦合度将会越来越高&#xff0c;系统的复杂度也将随之增加。这一讲我将以电商系统中的表结构设计为例…

基于docker的onlyoffice使用--运行JavaSpringExample

背景 我之前看到有开源项目很好地集成了onlyoffice&#xff0c;效果要比kkfilepreview好&#xff08;应当说应用场景不太一样&#xff09;。本文是在window10环境&#xff0c;安装完Docker Desktop的基础上运行onlyoffice&#xff0c;并利用官网JavaSpringExample进行了集成。 …

java学习part24异常throws

127-异常处理-异常处理方式二&#xff1a;throws_哔哩哔哩_bilibili 1.方法throws 2.如何抉择try和throws 3.手动throw语句 抛出一些java语法上没错但是不符合实际情况的异常。 用throw手动抛&#xff0c;方法上必须加throws。除非是运行时异常。 4.自定义异常

Android帝国之进程杀手--lmkd

本文概要 这是Android系统启动的第三篇文章&#xff0c;本文以自述的方式来讲解lmkd进程&#xff0c;通过本文您将了解到lmkd进程在安卓系统中存在的意义&#xff0c;以及它是如何杀进程的。&#xff08;文中的代码是基于android13&#xff09; 我是谁 init&#xff1a;“大…

Android控件全解手册 - 多语言切换完美解决方案(兼容7.0以上版本)

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…