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

文章目录

  • 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),这不就跟源码一样了嘛
源码:

在这里插入图片描述

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

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

相关文章

计算机网络(7):网络安全

网络安全问题 计算机网络上的通信面临以下的四种威胁: (1)截获(interception)攻击者从网络上窃听他人的通信内容。 (2)中断(interruption)攻击者有意中断他人在网络上的通信。 (3)篡改(modification)攻击者故意篡改网络上传送的报文。 (4)伪造(fabrication)攻击者伪造信息在网…

uniapp中uview组件库CircleProgress 圆形进度条丰富的使用方法

目录 #内部实现 #平台差异说明 #基本使用 #设置圆环的动画时间 #API #Props 展示操作或任务的当前进度&#xff0c;比如上传文件&#xff0c;是一个圆形的进度环。 #内部实现 组件内部通过canvas实现&#xff0c;有更好的性能和通用性。 #平台差异说明 AppH5微信小程…

web——德州扑克

1.此案例只用于学习 2.未接入游戏规则 HTML代码部分 <!DOCTYPE html> <html><head><meta charset"utf-8"><meta name"viewport" content"widthdevice-width"><meta name"Poker Skin" content&quo…

软件测试|深入学习 Docker Logs

简介 Docker 是一种流行的容器化技术&#xff0c;它能够帮助用户将应用程序及其依赖项打包成一个可移植的容器。Docker logs 是 Docker 提供的用于管理容器日志的命令&#xff0c;本文将深入学习 Docker logs 的使用和管理&#xff0c;帮助用户更好地监测和解决容器问题。 Do…

Python如何生成个性二维码

Python-生成个性二维码 一、问题描述 通过调用MyQR模块来实现生成个人所需二维码。 安装&#xff1a; pip install myqr 二、代码实现 1.普通二维码 from MyQR import myqr # 普通二维码 myqr.run(wordshttp://www.csdn.net/mayi0312,save_nameqrcode.png ) 效果图&#…

e2studio开发STHS34PF80人体存在传感器(1)----获取人体存在状态

e2studio开发STHS34PF80人体存在传感器.1--获取人体存在状态 概述视频教学样品申请完整代码下载主要特点硬件准备接口最小系统图新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_SCI_UART_Open()函…

百度地图打点性能优化(海量点、mapv)

文章目录 百度地图打点性能优化&#xff08;海量点、mapv&#xff09;原因优化方法数据获取方面页面加载方面 参考资料 百度地图打点性能优化&#xff08;海量点、mapv&#xff09; 原因 在百度地图api中&#xff0c;默认的点是下图的红点 而这种点位比较多的时候&#xff0c…

关键字:super关键字

在 Java 中&#xff0c;super 关键字主要有以下两种用法&#xff1a; 在子类中调用父类的构造方法&#xff1a;当创建子类对象时&#xff0c;可以使用 super 关键字来显式调用父类的构造方法。这可以用于初始化父类的成员变量或执行父类的其他初始化操作。下面是一个示例代码&…

腾讯云2核2G3M服务器可以运行几个网站?

在探讨这个问题之前&#xff0c;我们需要先了解网站运行所需的基本资源。一个网站的运行通常需要以下几个方面的资源&#xff1a;CPU、内存、磁盘和网络。接下来&#xff0c;我们将分析这些资源在不同配置下的使用情况&#xff0c;以确定腾讯云2核2G3M服务器可以运行多少个网站…

并发(4)

目录 16.sychronized修饰方法在抛出异常时&#xff0c;会释放锁吗&#xff1f; 17.多个线程等待同一个sychronized锁的时候&#xff0c;JVM如何选择下一个获取锁的线程&#xff1f; 18.sychronized是公平锁吗&#xff1f; 19.volatile关键字的作用是什么&#xff1f; 20.vo…

基于日照时数计算逐日太阳辐射

基于日照时数计算逐日太阳辐射

分布式【Zookeeper】

1.1 ZooKeeper 是什么 ZooKeeper 是 Apache 的顶级项目。ZooKeeper 为分布式应用提供了高效且可靠的分布式协调服务&#xff0c;提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。在解决分布式数据一致性方面&#xff0c;ZooKeeper 并没有直接采用 Paxos 算法&…

安全加固之weblogic屏蔽T3协议

一、前言 开放weblogic控制台的7001端口&#xff0c;默认会开启T3协议服务&#xff0c;T3协议则会触发的Weblogic Server WLS Core Components中存在反序列化漏洞&#xff0c;攻击者可以发送构造的恶意T3协议数据&#xff0c;获取目标服务器权限。 本文介绍通过控制T3协议的访问…

vue3 的内置组件汇总

官方给出的说明&#xff1a; Fragment: Vue 3 组件不再要求有一个唯一的根节点&#xff0c;清除了很多无用的占位 div。Teleport: 允许组件渲染在别的元素内&#xff0c;主要开发弹窗组件的时候特别有用。Suspense: 异步组件&#xff0c;更方便开发有异步请求的组件。 一、fr…

商品砍价系统设计原理与实践:技术解析与注意事项

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

SpringBoot 调用mybatis报错:Invalid bound statement (not found):

启动SpringBoot报错&#xff1a;Invalid bound statement (not found): 参考此文排查 命中了第6条 记录一手坑爹的Invalid bound statement (not found)&#xff08;六个方面&#xff09; mapper文件路径配置错误 订正以后 问题解决

GLSL着色器入门(持续更新中...)

目录 第一章&#xff1a;OpenGL works with triangles 第二章&#xff1a; Parallel Processing 第章 推荐来自b站的课程004 GLSL is not Javascript_哔哩哔哩_bilibili 第一章&#xff1a;OpenGL works with triangles 当我们谈论GLSL着色器时&#xff0c;其实就是在说怎么…

开发个小破软件——网址导航,解压就能用

网址导航 网站导航也称链接目录&#xff0c;将网站地址或系统地址分类&#xff0c;以列表、图文等形式呈现&#xff0c;帮助快速找到需要的地址。 应用场景 高效查找&#xff1a;网址导航是很好的入口&#xff0c;通过分类清晰的网站推荐&#xff0c;可以迅速访问网站资源。…

数据的创建、调用、修改、删除存储过程,以及第一类丢失更新(回滚丢失)和 第二类丢失更新(覆盖丢失/两次更新问题)

数据的创建存储过程、调用存储过程、修改存储过程、删除存储过程&#xff0c;以及第一类丢失更新&#xff08;回滚丢失&#xff09;和 第二类丢失更新&#xff08;覆盖丢失/两次更新问题&#xff09; 文章目录 一、创建存储的语法二、调用存储过程三、修改存储过程四、删除存储…

【python实战】python一行代码,实现文件共享服务器

一行代码实现文件共享 在一个局域网内&#xff0c;需要共享一个文件夹里内容。 我们可以在任意一台有python环境的电脑上&#xff0c;迅速架起一个http协议的服务&#xff0c;然后将文件夹里的文件内容共享出来。是的仅仅需要一行代码 就是这么简单 把电脑的相关项目文件通…