ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【C语言深度剖析】第七篇:可变参数列表

2022-01-18 18:02:15  阅读:162  来源: 互联网

标签:va int max 可变 C语言 剖析 第七篇 参数 arg


文章目录


一、基本使用

首先看下面代码:

#include <stdio.h>

int GetMax(int x, int y)
{
	if (x > y)
	{
		return x;
	}

	return y;
}

int main()
{
	int x = 0;
	int y = 0;
	scanf("%d %d", &x, &y);

	int max = GetMax(x, y);

	printf("max = %d\n", max);
	return 0;
}

在这里插入图片描述
这个很容易理解,求两个数当中的最大值,但是此时我想要求不确定的元素个数的大小,就是说我们不知道元素的个数。那么应该怎么办呢?
可变参数我们C语言中一直在用,比如说:scanf 和 printf
在这里插入图片描述
在这里插入图片描述
其中…就代表了可变参数。这就很容易理解了,我们在C语言中使用输入输出函数的时候,不知道元素的个数,但是依然还是能够成功运行。
那么我们也可以设计一个函数类似scanf 和 printf一样。
看一下它的使用:

#include <stdio.h>
#include <windows.h>

//可变参数列表至少要有一个参数
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 x = va_arg(arg, int);

		if (max < x)
		{
			max = x;
		}
	}
	va_end(arg);//arg使用完毕,收尾工作。本质就是讲arg指向NULL
	return max;
}


int main()
{
	int max = FindMax(5, 1, 2, 3, 4, 5);//查找5个数当中的最大值
	printf("max = %d\n", max);

	return 0;
}

在这里插入图片描述

注意:可变参数列表至少有一个参数

二、原理

首先还是这串代码,我们一步一步分析

#include <stdio.h>
#include <windows.h>

//可变参数列表至少要有一个参数
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 x = va_arg(arg, int);

		if (max < x)
		{
			max = x;
		}
	}
	va_end(arg);//arg使用完毕,收尾工作。本质就是讲arg指向NULL
	return max;
}


int main()
{
	int max = FindMax(5, 1, 2, 3, 4, 5);//查找5个数当中的最大值
	printf("max = %d\n", max);

	return 0;
}

第一个就是va_start,是一个char*的指针
在这里插入图片描述
然后就是va_end,作用就是将指针置为空
在这里插入图片描述

在这里插入图片描述
然后通过汇编角度来理解:
在这里插入图片描述
在这里插入图片描述
此时我们将整形改为字符型:

#include <stdio.h>
#include <windows.h>

//可变参数列表至少要有一个参数
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 x = va_arg(arg, int);

		if (max < x)
		{
			max = x;
		}
	}
	va_end(arg);//arg使用完毕,收尾工作。本质就是讲arg指向NULL
	return max;
}


int main()
{
	//int max = FindMax(5, 1, 2, 3, 4, 5);//查找5个数当中的最大值
	int max = FindMax(5, 'a', 'b', 'c', 'd', 'e');//查找5个数当中的最大值
	printf("max = %d\n", max);

	return 0;
}

在这里插入图片描述
为什么可以呢?int max = va_arg(arg, int)难道不应该改为int max = av_arg(arg, char)吗?不是应该按照一个字节读取吗?
我们同样可以转到反汇编:
在这里插入图片描述
在往下走一步:
在这里插入图片描述
可见在可变参数压栈的时候,如果是短整形,一般都要进行int类型提升。所以编译器会自动进行整形提升,我们如果改为了char就会出错。
注意事项

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

typedef char * va_list;
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end
//这个宏特别好理解,结合栈帧中临时参数的压入位置
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
//这个设计特别巧妙,先让ap指向下个元素,然后使用相对位置-偏移量,访问当前元素。
//访问了当前数据的同时,还让ap指向了后续元素,一举两得。
#define _crt_va_arg(ap,t) ( (t )((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
//这个宏特别好理解,将ap指针设置为NULL
#define _crt_va_end(ap) ( ap = (va_list)0 )
//取参数的地址,也很好理解
#define _ADDRESSOF(v) ( &(v) )
//难点是这个,不太好理解
#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)的意思:计算一个最小数字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字节对齐:
结合之前栈帧的学习和上面代码的测试结果
怎么办到的:
第一步理解:4的倍数,既然是4的最小整数倍取整,那么本质是:x=4
m,m是具体几倍。对7来讲,m就是2,对齐的结果就是8,而m具体是多少,取决于n是多少
如果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,1<=r<4 那么m就是 (n+4-1)/4->(能整除4部分+r+3)/4,其中4<=r+3<7 -> 能整除4部分/4 + (r+3)/4 -> n/4+1
第二步理解:最小4字节对齐数
搞清楚了满足条件最小是几倍问题,那么,计算一个最小数字x,满足 x>=n && x%4==0,就变成了
((n+sizeof(int)-1)/sizeof(int))[最小几倍] * sizeof(int)[单位大小] -> ((n+4-1)/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) ),无需先/,在

标签:va,int,max,可变,C语言,剖析,第七篇,参数,arg
来源: https://blog.csdn.net/qq_52809807/article/details/122423937

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有