【可变参数列表如何可变?】

简介: 【可变参数列表如何可变?】

本章重点


学会使用可变参数列表的使用与原理


函数传参补充知识


  • 如果函数没有形式参数,仍然可以给函数传递参数。
  • 在c语言中,只要发生了函数调用并且传递了函数,必定形成临时变量。
  • 所谓的临时拷贝本质就是在栈帧内部形成的,从右向左依次形成临时拷贝(变量)。


求两个数据中的最大值


#include <stdio.h>
//求两个数据中的最大值
int FindMax(int x, int y)
{
    if (x > y) {
        return x;
    }
    return y;
}
int main()
{
    int x = 0;
    int y = 0;
    printf("Please Eneter Two Data# ");
    scanf("%d %d", &x, &y);
    int max = FindMax(x, y);
    printf("max = %d\n", max);
    return 0;
}


  • 如果未来我们的需求变了,不再求固定的数据个数的最大值
  • 而是求任意多个数据中的最大值(至少一个),要求不能使用数组
  • 因为目前参数个数不确定,那么函数编写的时候,参数个数也无法确定,换句话说,函数也就没法编写
  • 不过,C提供了满足该场景的解决方案:可变参数列表
#include <stdio.h>
#include <windows.h>
//num:表示传入参数的个数
//可变参数列表至少有一个参数
int FindMax(int num, ...)//可变参数列表
{
  va_list arg; //定义可以访问可变参数部分的变量,其实是一个char*类型
  va_start(arg, num); //使arg指向可变参数部分
  int max = va_arg(arg, int); //根据类型,获取可变参数列表中的第一个数据
  int i = 0;
  for (i = 0; i < num - 1; i++) {//获取并比较其他的
    int curr = va_arg(arg, int);
    if (max < curr) {
      max = curr;
    }
  }
  va_end(arg); //arg使用完毕,收尾工作。本质就是将arg指向NULL
  return max;
}
int main()
{
  int max = FindMax(5, 11, 22, 33, 44, 55);
  printf("max = %d\n", max);//55
  system("pause");
  return 0;
}


  1. 使用va_list类型的变量声明一个可变参数列表的访问指针,通常命名为arg。
  2. 使用va_start(arg, last_fixed_param)宏来初始化可变参数列表。last_fixed_param是最后一个固定参数的名称,也就是num,在这个参数之后是可变数量的参数。这个宏会将arg指针指向可变参数列表的起始位置。
  3. 使用va_arg(arg, type)宏来获取可变参数列表中的参数值。type是参数的数据类型。该宏从可变参数列表中获取下一个参数的值,并将arg指针移动到下一个参数的位置。
  4. 使用va_end(arg)宏来结束对可变参数列表的访问。这个宏执行一些必要的清理工作,并将arg指针置为NULL。



如果将参数改成char类型,求char类型变量中的最大值,代码会有问题吗?


#include <stdio.h>
#include <windows.h>
//num:表示传入参数的个数
int FindMax(int num, ...)
{
  va_list arg; //定义可以访问可变参数部分的变量,其实是一个char*类型
  va_start(arg, num); //使arg指向可变参数部分
  int max = va_arg(arg, int); //根据类型,获取可变参数列表中的第一个数据
  for (int i = 0; i < num - 1; i++) {//获取并比较其他的
    int curr = va_arg(arg, int);//va_arg(arg, char),这是不正确的
    if (max < curr) {
      max = curr;
    }
  }
  va_end(arg); //arg使用完毕,收尾工作。本质就是讲arg指向NULL
  return max;
}
int main()
{
  char a = '1'; //ascii值: 49
  char b = '2'; //ascii值: 50
  char c = '3'; //ascii值: 51
  char d = '4'; //ascii值: 52
  char e = '5'; //ascii值: 53
  int max = FindMax(5, a, b, c, d, e);
  printf("max = %d\n", max);
  system("pause");
  return 0;
}


先来解释一下汇编代码中的movsx是什么意思?



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


1. 实际传入的参数如果是char,short,float,编译器在编译的时候,会自动进行提升(通过movsx指令进行到当前计算机寄存器的位数)


2. 函数内部使用的时候,根据类型提取数据,更多的是通过int或者double来进行。


注意事项


  • 可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途终止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那是不行的。
  • 参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用 va_start 。
  • 这些宏是无法直接判断实际存在参数的数量。
  • 这些宏无法判断每个参数的类型。
  • 如果在 va_arg 中指定了错误的类型,那么其后果是不可预测的。


原理


  1. 可变参数列表对应的函数,最终调用也是函数调用,也要形成栈帧。
  2. 栈帧形成前,临时变量是要先入栈的,根据之前所学,参数之间位置关系是固定的。
  3. 通过上面汇编的学习,发现了短整型在可变参数部分,会默认进行整形提升,那么函数内部在提取该数据的时候,就要考虑提升之后的值,如果不加考虑,获取数据可能会报错或者结果不正确。


我们接下来就看看这几个宏的含义:


1、va_list arg;



2、va_start(arg, num);



3、va_end(arg);



现在我们来重点了解一下这个宏#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )


前提: 为了后面方便表述,我们假设sizeof(n)的值是n(char 1,short 2, int 4)


我们在32位平台,vs2013下测试,sizeof(int)大小是4,其他情况我们不考虑


根据上面学到的知识,_INTSIZEOF(n)中的参数可以是变量类型或者变量名。

我们来计算一下参数为char和short,该宏的值为多少?

#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )


结果都是4个字节


_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字节对齐:


             结合之前栈帧的学习、上面代码的测试结果和movsx指令我们就可以知道


怎么办到的:


相关文章
|
小程序 JavaScript 前端开发
【经验分享】如何获取任意小程序appId及页面路径
【经验分享】如何获取任意小程序appId及页面路径
1138 8
|
关系型数据库 MySQL
Mysql连接无效(invalid connection)解决方案
Mysql连接无效(invalid connection)解决方案
1757 0
Mysql连接无效(invalid connection)解决方案
|
存储 jenkins 持续交付
Docker Volume 之权限管理(转)
Volume数据卷是Docker的一个重要概念。数据卷是可供一个或多个容器使用的特殊目录,可以为容器应用存储提供有价值的特性: 持久化数据与容器的生命周期解耦:在容器删除之后数据卷中的内容可以保持。
2558 0
|
10月前
|
SQL 运维 Oracle
KDTS迁移视图报错ERROR: syntax error at or near "IF"
KDTS迁移视图报错ERROR: syntax error at or near "IF"
KDTS迁移视图报错ERROR: syntax error at or near "IF"
|
监控 应用服务中间件 BI
nginx日志统计分析自动报表工具goaccess(推荐)
## 功能描述 - nginx日志统计分析自动报表工具goaccess(推荐) - 网站总访问量统计,按天统计访问量,按页面(不同URL)统计访问量(不包括JS、css),按静态页面统计访问量(包括JS、css),不存在的页面统计访问量 - 按不同的IP统计访问量,按不同的操作系统统计访问量,...
8057 0
|
10月前
|
人工智能 搜索推荐 区块链
元宇宙:虚拟现实的新纪元
在科技飞速发展的今天,元宇宙(Metaverse)正从科幻走向现实,成为连接物理与数字世界的桥梁。本文将探讨元宇宙的定义、关键技术、现有形态及未来展望,带您领略这一虚拟空间的无限可能。元宇宙利用VR、AR、区块链等技术,构建了一个沉浸式的虚拟世界,用户可通过数字化身自由探索、社交、娱乐、学习和工作。尽管仍处初级阶段,但虚拟会议、数字艺术、游戏娱乐等领域已初现端倪。未来,元宇宙有望成为第二人生、数字商务、创新教育和远程协作的重要平台,同时也面临数据安全、隐私保护等挑战。
|
10月前
|
SQL 运维 网络安全
【实践】基于Hologres+Flink搭建GitHub实时数据查询
本文介绍了如何利用Flink和Hologres构建GitHub公开事件数据的实时数仓,并对接BI工具实现数据实时分析。流程包括创建VPC、Hologres、OSS、Flink实例,配置Hologres内部表,通过Flink实时写入数据至Hologres,查询实时数据,以及清理资源等步骤。
|
存储 监控 安全
Linux存储安全:保护你的数据免受威胁
【8月更文挑战第18天】在数字化时代,数据安全至关重要。Linux以稳定与安全著称,但仍需强化存储保护。本文概览Linux存储安全,涵盖物理安全、文件系统选择、数据加密技术如LUKS与eCryptfs、精细访问控制及审计监控等最佳实践,辅以定期更新、网络隔离、安全协议运用、备份及用户培训,全方位守护数据安全。通过这些措施,可有效防御未授权访问与数据损失,确保信息资产安全无忧。
209 1
|
小程序 JavaScript 前端开发
微信小程序更新操作失效解决和微信小程序引入echarts
该文介绍了微信小程序的两个问题及解决方案:1)当更新云开发数据库操作无效(返回更新0条数据)时,需检查并修改数据表的权限设置,确保`read`和`write`均为`true`。2)如何引入Echarts:从[GitHub](https://github.com/ecomfe/echarts-for-weixin)下载微信版Echarts,然后在小程序中引入组件,配置`app.json`,并在页面JS中导入并使用Echarts,提供示例代码展示柱状图、折线图和饼图的实现。
357 1
|
Java 关系型数据库 MySQL
【已解决】SpringBoot 启动报错:Failed to configure a DataSource: ‘url‘ attribute is not specified and no emb
【已解决】SpringBoot 启动报错:Failed to configure a DataSource: ‘url‘ attribute is not specified and no emb
6953 1