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

在这里插入图片描述

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

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

相关文章

【Vue3+React18+TS4】1-1 : 课程介绍与学习指南

本书目录&#xff1a;点击进入 一、为什么做这样一门课程? 二、本门课的亮点有哪些? 2.1、轻松驾驭 2.2、体系系统 2.3、高效快捷 2.4、融合贯通 三、课程内容包括哪些? 四、项目实战 《在线考勤系统》 五、课适合哪些同学? 一、为什么做这样一门课程? 近十年内前端…

git使用(完整流程)

1. 新建仓库 1.右击 git bash 后 输入 git init (仓库为:当前目录) git init name (仓库为:name文件夹) git clone https://github.com/Winnie996/calculate.git //https2.工作区域 工作目录 3. 添加 提交 git add . //工作区添加至暂存区 git commit -m "注释内容&q…

Android14之Selinux解决neverallow报错(一百七十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

Kafka消息阻塞:拯救面试的八大终极解决方案!

大家好&#xff0c;我是小米&#xff0c;一个对技术充满热情的90后程序员。最近在准备社招面试的过程中&#xff0c;遇到了一个超级有挑战性的问题&#xff1a;“Kafka消息阻塞怎么解决&#xff1f;”今天&#xff0c;我就来和大家一起深入剖析这个问题&#xff0c;分享我在解决…

kubernetes(k8s)集群常用指令

基础控制指令 # 查看对应资源: 状态 $ kubectl get <SOURCE_NAME> -n <NAMESPACE> -o wide 查看默认命名空间的pod [rootk8s-master ~]# kubectl get pod NAME READY STATUS RESTARTS AGE nginx 1/1 Running 0 3h53m查看所有pod [roo…

Vue3-36-路由-路由的元数据信息 meta

什么是 meta 简单的理解&#xff0c;meta 就是路由对象 的一个属性对象&#xff0c; 可以 通过这个 属性给 路由对象添加 一些必要的属性值&#xff0c; 在使用路由对象时可以获取到这个属性型对象&#xff0c;从而进行一些其他的逻辑判断。 meta 这个非常的简单&#xff0c;就…

深入了解ReadDirectoryChangesW并应用其监控文件目录

简介 监视指定目录的更改&#xff0c;并将有关更改的信息打印到控制台&#xff0c;该功能的实现不仅可以在内核层&#xff0c;在应用层同样可以。程序中使用 ReadDirectoryChangesW 函数来监视目录中的更改&#xff0c;并使用 FILE_NOTIFY_INFORMATION 结构来获取有关更改的信息…

BERT(从理论到实践): Bidirectional Encoder Representations from Transformers【1】

预训练模型:A pre-trained model is a saved network that was previously trained on a large dataset, typically on a large-scale image-classification task. You either use the pretrained model as is or use transfer learning to customize this model to a given t…

Python中的装饰器

顾名思义&#xff0c;函数装饰器就是对这个函数进行了装饰&#xff0c;比如在函数的前后进行日志打印等。在Python中&#xff0c;装饰器是一种特殊的语法&#xff0c;用于简化函数或方法的定义和调用。装饰器允许你在不修改原始函数代码的情况下&#xff0c;通过在其上应用装饰…

Apple M2 Pro芯片 + docker-compose up + mysql、elasticsearch pull失败问题的解法

背景 &#xff08;1&#xff09;从github上git clone了一个基于Spring Boot的Java项目&#xff0c;查看readme&#xff0c;发现要在项目的根目录下&#xff0c;执行“docker-compose up”。&#xff08;2&#xff09;执行“docker-compose up”的前提是&#xff0c;在macos上要…

Vue中break关键字

Change() {//每次触发该事件&#xff0c;都要讲data重新赋值一次this.data JSON.parse(JSON.stringify(this.data1));// 根据选中的等级更新数据switch (this.selectedlevel) {case 1:// 更新数据为一级数据this.data this.data.filter(item > item.level "1"…

【shell漫步】3 条件分支结构

碎碎念 接上文的运算符的内容&#xff0c;这一章终于开始接触控制结构 【shell漫步】2 运算符-CSDN博客 分支结构的写法 当我们要对不同情况采取不同措施的时候就要用到分支结构 在shell中分支结构的写法如下 if [ 情况1 ] then代码1 elif [ 情况2 ] then代码2 elif [ 情…

mysql四大引擎、账号管理以及建库

目录 一.数据库存储引擎1.1存储引擎的查看1.2InnoDB1.3MyISAM1.4 MEMORY1.5 Archive 二.数据库管理2.1元数据库分类2.2 操作2.3 MySQL库 三.数据表管理3.1三大范式3.2 整形3.3 实数3.4 字符串3.5 text&blob3.6 日期类型3.7 选中标识符 四.数据库账号管理4.1 查询用户4.2查看…

【论文阅读|冷冻电镜】DISCA: High-throughput cryo-ET structural pattern mining

论文题目 High-throughput cryo-ET structural pattern mining by unsupervised deep iterative subtomogram clustering 摘要 现有的结构排序算法的吞吐量低&#xff0c;或者由于依赖于可用模板和手动标签而固有地受到限制。本文提出了一种高吞吐量的、无需模板和标签的深度…

Kotlin协程学习之-01

由于协程需要支持挂起、恢复、因此对于挂起点的状态保存就显得机器关键。类似的&#xff0c;线程会因为CPU调度权的切换而被中断&#xff0c;它的中断状态会保存在调用栈当中&#xff0c;因而协程的实现也按照是否开辟相应的调用栈存在以下两种类型&#xff1a; 有栈协程&…

Zookeeper注册中心实战

Java学习手册面试指南&#xff1a;https://javaxiaobear.cn Spring Cloud Zookeeper通过自动配置和绑定到 Spring 环境和其他 Spring 编程模型习惯用法&#xff0c;为 Spring Boot 应用程序提供Apache Zookeeper集成。通过一些简单的注释&#xff0c;您可以快速启用和配置应用…

【RK3399 PCIE调试——硬件信息资源获取】

一、1、 硬件接口 二、2、 PCB原理图 三、 官网地址&#xff1a; https://t.rock-chips.com/portal.php 相关资料和固件烧写可参考资料下载菜单

WPF 使用矢量字体图标

矢量字体图标 在WPF项目中经常需要显示图标&#xff0c;但是项目改动后&#xff0c;有时候需要替换和修改图标&#xff0c;这样非常麻烦且消耗开发和美工的时间。为了快速开发项目&#xff0c;节省项目时间&#xff0c;使用图标矢量字体图标是一个非常不错的选择。 矢量字体图标…

github 好项目 之 reference

github项目地址 网页网址 点进去以后你可以看到很多关于技术前沿的东西的简单笔记&#xff0c;一些实践的代码&#xff0c;或者是一些快捷键的命令 我个人比较喜欢 latex 的数学公式笔记 以及关于 vim 的一些命令 还有我最喜欢的git命令

算法训练第六十天|84.柱状图中最大的矩形

84.柱状图中最大的矩形&#xff1a; 题目链接 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 : 输入&#xff1a;heights [2,1,5,6,2,3] 输出…