【C语言】可变参数列表

2023-11-11


前言

可变参数列表,使用起来像是数组,学习过函数栈帧的话可以发现实际上他也就是在栈区定义的一块空间当中连续访问,不过他不支持直接在中间部分访问。


声明: 以下所有测试都是在x86,vs2013下完成的。

一、可变参数列表是什么?


在这里插入图片描述
在我们初始C语言的第一节课的时候我们就已经接触了可变参数列表,在printf的过程当中我们通常可以传递大量要打印的参数,但是我们却不知道他是如何做到的,今天就带大家剖析它。



二、怎么用可变参数列表


首先我们要引入windows.h的头文件
然后我们先要介绍以下几个宏。在这里我们先简述它的功能,在后面会有详细的讲解,这里是为了方便大家入门。

typedef char* va_list;  //类型的重定义

#define _ADDRESSOF(v) (&(v))//一个取地址的宏。

1._ADDRESSOF:取传入变量的地址。

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




2._INTSIZEOF:该宏功能是让n的类型往4的倍数上取整。

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

下面一段代码进行解释:

#pragma pack(1)//设置默认对其数为1
struct A
{
	char ch[11];
};

int main()
{
	printf("int : %d\n", _INTSIZEOF(int));
	printf("double: %d\n", _INTSIZEOF(double));
	printf("short: %d\n", _INTSIZEOF(short));
	printf("float: %d\n", _INTSIZEOF(float));
	printf("long long int: %d\n", _INTSIZEOF(long long int));
	printf("struct A:%d\n", _INTSIZEOF(struct A));
	return 0;
}

结果:
在这里插入图片描述


3.__crt_va_start_a:取变量v的地址强转为char*然后向指向v类型对其数后,即找到第一个可变参数列表当中的变量!

#define __crt_va_start_a(ap, v) \
((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))



4.__crt_va_arg:将ap提前指向下一个要访问的位置,并且返回当前访问的内容。 注意+=后ap指向下一个要访问的地址,但是返回的内容是当前的。

#define __crt_va_arg(ap, t)   \
      (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))



5.__crt_va_end:将ap置成NULL。

#define __crt_va_end(ap)\
 ((void)(ap = (va_list)0))

紧接着我们看一下以下几个定义。
#define va_start __crt_va_start
#define va_arg   __crt_va_arg
#define va_end   __crt_va_end

测试:找一组不存放在数组当中的最大的一个数据返回。

int Find_Max(int num, ...)
{
	//定义一个char* 的变量arg
	va_list arg;
	//将arg指向第一个可变参数
	va_start(arg, num);
	//将max置成第一个可变参数,然后arg指向下一个可变参数
	int max = va_arg(arg, int);
	//循环num-1次,访问完剩下的可变参,找到最大的赋值给max
	for (int i = 1; i < num; ++i)
	{
		int r;
		if (max < (r = va_arg(arg, int)))
		{
			max = r;
		}
	}
	//将arg指针变量置成NULL,避免野指针
	va_end(arg);
	return max;
}
int main()
{
	int ret = Find_Max(5, 0x1, 0x2, 0x3, 0x4, 0x5);
	printf("ret :%d\n", ret);
	return 0;
}

结果:
在这里插入图片描述


三、对于宏的深度剖析


虽然在Linux下的进程地址空间是由高到低排列的,但是由于vs下的内存是从低字节到高字节的,我们的栈会和linux下画的不太一样,但是都是朝着低地址方向扩展的。这是方便大家理解。

Linux的进程地址空间示意图:
在这里插入图片描述
代码栈帧示意图:
在这里插入图片描述


隐式类型转换


举个栗子,当我们执行下面的代码,当我们以char类型传参,但函数体依旧以int的步长获取,此时会出错吗?

#include<stdio.h>
#include<windows.h>
int Find_Max(int num, ...)
{
	//定义一个char* 的变量arg
	va_list arg;
	//将arg指向第一个可变参数
	va_start(arg, num);
	//将max置成第一个可变参数,然后arg指向下一个可变参数
	int max = va_arg(arg, int);
	//循环num-1次,访问完剩下的可变参,找到最大的赋值给max
	for (int i = 1; i < num; ++i)
	{
		int r;
		if (max < (r = va_arg(arg, int)))
		{
			max = r;
		}
	}
	//将arg指针变量置成NULL,避免野指针
	va_end(arg);
	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 ret = Find_Max(5, a, b, c, d, e);
	//int ret = Find_Max(5, 0x1, 0x2, 0x3, 0x4, 0x5);
	printf("ret :%d\n", ret);
	system("pause");
}

答案:
不会的,由于压栈的时候是通过寄存器传参的,32位下的寄存器就是4个字节。
在这里插入图片描述

压栈时的汇编:其中第一条不是mov,而是movsx,即汇编语言数据传送指令MOV的变体。带符号扩展,并传送。也就是整形提升。
在这里插入图片描述
同理:用float传参,用double字长走,也是没有问题的。
在这里插入图片描述
总结
所以我们习惯在函数体内部(Find_Max)用int/double为长度走,而传参的时候我们可以用char/short/float等等类型。

注意:
64位下的定义和32位下差异是很大的。

为什么按照4字节对齐:
先前讲到在短整型,在压栈的过程中会发生整形提升,那么从栈帧中要拿到对应的数据也要按照对应的方法提取。

_INTSIZEOF的数学理解:

_INTSIZEOF(n)的意思:计算一个最小数字x,满足 x>=n && x%4==0,n表示sizeof(n)的值。即该类型的大小要满足往n的整数倍对齐,且最小不能小于n。

以4字节对齐为栗子:
n%4 == 0,则 ret = n;
n %4 != 0 , 则 ret = (n+ 4 - 1)/4 *4;
(n+ 4 - 1)/4 -->假设 n为1到4,那么(n + 4 - 1)/4的结果都是1,再乘上对其数4就是以4对齐的最小对齐数了。就能将这4个数值范围最小对齐倍数控制在同一个值。

我们观察(n+ 4 -1)/4 *4,/4实际上就是将二进制序列往右移,*4就是把二进制序列往左移动,这一来一回实际上就是把最低两位置成0,那么我们还可以简化成:
(n+ 4 -1) & ~3 ,也就是源码当中的定义了!!

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



对两个函数的重新认知


对printf的理解:
在上述的例子中,宏是无法判断实际存在参数的数量,以及实际参数的类型的,那么在printf当中,必定有能够确定参数数量以及辨别参数类型的方法,实际上也就是%c,%d,%lf,其中%的数量除了%%外的%的数量实际上就能让我们得知参数的数量,而%c,%d,实际上也就说明了对应的类型。


对exec系列的理解:
在这里插入图片描述
进程控制,当时讲述了实际上只有一个系统调用execve,其他函数exec函数最终都是要调用execve函数,那么是如何实现从参数lv这个过程的呢?
答案:
实际上访问到null为止,传参的数量用一个count一直计数就能拿到,而类型毫无疑问就是char*,我们可以用strlen去计算要走多长。(不过两个char数组通常会间隔多8个字节)


总结

本章的可参数列表就到此结束啦,下期可能会重新对函数栈帧的理解发布一篇博客,在这里博主祝大家新年快乐!!!
一键三连一键三连一键三连一键三连一键三连

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【C语言】可变参数列表 的相关文章

随机推荐

  • MES管理系统中的看板管理如何提高生产效率

    在制造企业中 生产效率是关系到企业运营效率和成本的关键因素 而MES Manufacturing Execution System 制造执行系统 作为一种现代化的生产管理系统 其看板管理功能对于提高生产效率具有重要作用 下面 我们将详细介绍
  • C# Yield

    https blogs msdn microsoft com oldnewthing 20080814 00 p 21243 https blogs msdn microsoft com oldnewthing 20080815 00 p
  • NIM Server、Client交互操作过程分析——暨NIM防火墙要求

    最后更新2021 08 10 nimsh端口 从AIX 5 2 TL7开始 NIM增加了 nimsh 功能 此功能是原有NIM基于rsh协议操作的升级版 众所周知 rsh协议没有加密传输功能 安全性也很差 nimsh对此进行了升级 以提供更
  • lowbit

    lowbit用来计算二进制数 从右往左数第一个1与其后面的0组成的数 int lowbit int x return x x x 12 1100 lowbit 12 100 4 7 111 lowbit 7 1 1
  • Flutter优秀第三方常用框架

    名称 GitHub地址 下拉刷新上拉加载 EasyRefresh 下拉刷新上拉加载 PullToRefresh SharedPreferences shared preferences 中国城市选择器 city picker 设备信息 de
  • 硬盘存储知识

    存储知识 内存和外存 硬盘 1 物理磁盘类型 硬盘分为 机械硬盘 HDD 和固态硬盘 SSD 注意 买硬盘的时候要注意转速 机械硬盘是以下三种 物理磁盘类型 SATA盘 物理磁盘类型 SAS盘 物理磁盘类型 NL SAS盘 固态硬盘 物理磁
  • 微信小程序期末大作业 中草药小程序 药海拾遗

    微信小程序期末大作业 中草药小程序 药海拾遗 小程序详情如下 下载链接在文末 学习社区可以自己添加内容 点我下载资源 https download csdn net download weixin 43474701 59675965
  • 【Struts2六】ui标签之form标签及数据回显

    ui标签 用在jsp页面用于回显数据的标签 这些标签是由框架定义的 用来替代原生的标签 ui标签有
  • WPF编程,Live Charts使用说明(11)——基本折线图

    后台 using System using System Windows Controls using System Windows Media using LiveCharts using LiveCharts Wpf namespace
  • Spring整合Druid

    Druid是Java语言中最好的数据库连接池 Druid能够提供强大的监控和扩展功能 Druid是阿里巴巴开源平台上的一个项目 整个项目由数据库连接池 插件框架和SQL解析器组成 该项目主要是为了扩展JDBC的一些限制 可以让程序员实现一些
  • 厉害了|十分钟掌握python3语言特性

    看了王垠的 如何掌握所有程序语言 感触甚深 如果说程序语言有其通用规律的话 那就是语言特性 也就是这些语言的通用概念 这些概念的具体语法的形式可能都不一样 但是所内涵的功能是一致的 比如英语中的bird和汉语中鸟 其实指的都是同一种事物 关
  • python自动生成电子邮箱'@hotmail.com', '@msn.com', '@yahoo.com', '@gmail.com', '@aim.com', '@aol.com', '@mail

    def getAutoEmail self 自动生成电子邮箱 Fist email join random sample string ascii letters string digits 9 last emailList hotmail
  • 使用Docker及Docker-compose部署SpringBoot项目

    1 环境准备 Windows下安装Docker需要WSL2及Hyper v Windows家庭版没有 Linux下安装Docker 参考官方文档 Install Docker Engine Docker Documentation 根据自己
  • Poi实现Excel导出

    Poi实现Excel导出 Appache Poi提供了HSSFWorkbook操作2003版本的Excel文件 XSSFWorkbook操作2007版Excel文件 简单的具体实现在网上有很多案例可以参考学习 我就不写入门案例了 下面我会将
  • AutoML系列

    本文是对 Neural Architecture Search A Survey 的翻译 这篇Paper 很好的总结分析了 NAS 这一领域的研究进展 摘要 在过去几年中 深度学习在各种任务上 例如图像识别 语音识别和机器翻译 取得了显著进
  • 利用javascript的算术运算符获取一个数字的每位数字

  • element tree 树形控件

    组件 Element 地址 http element eleme io zh CN component tree Tree树形控件
  • 【VAR模型

    向量自回归 VAR 是一种随机过程模型 用于捕获多个时间序列之间的线性相互依赖性 VAR 模型通过允许多个进化变量来概括单变量自回归模型 AR 模型 VAR 中的所有变量都以相同的方式进入模型 每个变量都有一个方程式 根据其自身的滞后值 其
  • IBM MQ 故障诊断(一)

    说明 本文主要是针对运维人员的手册 前面部分主要是应用三板斧的方式 后面的步骤可能会发散和具体深入一些 不过也不是严格的划分 读者就当看一遍杂文的方式来看待此文吧 一 队列管理器的启停 QMGR的启停是故障诊断中遇到最多的需求之一 启动队列
  • 【C语言】可变参数列表

    文章目录 前言 一 可变参数列表是什么 二 怎么用可变参数列表 三 对于宏的深度剖析 隐式类型转换 对两个函数的重新认知 总结 前言 可变参数列表 使用起来像是数组 学习过函数栈帧的话可以发现实际上他也就是在栈区定义的一块空间当中连续访问