首页 > 其他分享 >可变参数函数原理

可变参数函数原理

时间:2023-09-30 13:31:45浏览次数:32  
标签:va 函数 int max arg 参数 可变 我们

可变参数列表

我们想通过一个例子来引出我们这个话题.如果我们想要求两个数的最大值,这个函数是非常容易的.

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

int main()
{
     int a = 10;
     int b = 20;
     int max = GetMax(a, b);
     printf("max = %d\n", max);
     return 0;
}

但是突然有一天你想求10个、20个......数中的最大值,请问我们应该如何做,此时我们想很简单,我们是可以使用一个数组,可是有一天我们不想使用数组,就是想通过传入参数额形式来让我们完成,这就需要可变参数了.我们先来看一下,不用担心,后面都会和大家分析到.

int GetMax(int num,...)
{

}

先来说一下定义.在计算机程序设计,一个可变参数函数是指一个函数拥有不定引数,即是它接受一个可变数目的参数。不同的编程语言对可变参数函数的支持有很大差异。 一般而言,在设计函数时会遇到许多数学和逻辑操作,是需要一些可变功能。例如,计算数字串的总和、字符串的联接或其他操作过程,都可以存在任意数量的参数

我们是不是在之前使用过可变参数函数,是的,我们确实使用过,例如我们上文的格式化输入输出.

image-20230930115316155

int main()
{
  int a = 10;
  int b = 20;
  printf("%d\n", a);                     //可以打印一个
  printf("%d %d\n", a,b);            //也可以打印两个
  return 0;
}

可变参数原理

先来说一下可变参数的原理,我们知道当我们在函数调用进行传入参数的时候,请问我们参数是否会形成临时拷贝?一定会的,不仅仅会形成临时拷贝,而且是从右向左一个一个拷贝的,甚至VS系列中参数空间位置是紧邻的.

int main()
{
	int max = FindMax(5, 1, 2, 3, 9, 7);
	printf("max = %d\n", max);
	system("pause");
	return 0;
}

他的栈帧地址我们可以这样的画.

image-20230930120048618

这里我们开始解释我们可变参数的原理,我们也是可以通过栈帧来找到了每一个参数,试想一下,如果我们拿到了一个参数的地址,按照某种特定的规则,我们就可以得到所有的参数.

可变参数使用

根据上面的说法,我们可以自己实现一个寻找参数的机制,不过C语言已经早就帮助我们实现好了,这里我们看一下.

va_list 
va_start
va_arg
va_end   

首先这里面是四个宏,下面我们先说一下他们的用法,然后解释一下功能,最后基本的刨析原理.多的不说,我们先来看一下结果,后面和大家进行分析.

#include <stdarg.h>

int FindMax(int num, ...)
{
	va_list arg;
	va_start(arg, num);

	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);
	return max;
}

int main()
{
	int max = FindMax(5, 1, 2, 3, 9, 7);
	printf("max = %d\n", max);
	system("pause");
	return 0;
}

image-20230930120948651

va_list : 这是一个类型, 被重命名过  typedef char* va_list;
va_start: 这个暂停说下
va_arg  : 这个也暂停说
va_end  : 这个是只为空指针 
          #define va_end   __crt_va_end
          #define __crt_va_end(ap)        ((void)(ap = (va_list)0))

上面我们说了,既然我们是函数,那么函数的参数一定会被压入栈中,那么对于可变参数,由于我们的参数的个数是不确定的,所以我们必须手动传入的参数作为我们标识我们的参数有多少个,这就是我们第一个参数的值,所以我们绝对有能力知道所有的参数.

va_list就是一个类型,本质就是char* 的指针,至于为何是char类型的指针,这是因为他加上1只会跳过一个字节.我们想要这个指针指向我们的实际的参数位置,这里就是我们的va_start作用了.看一下他的定义.

#define va_start _crt_va_start
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

这里来解释一下_ADDRESSOF,这个就是给知道我们的世家的参数的位置提供的供能,具体等一下我们的谈.

这个我们暂停一下,先说结论,他的作用就是计算4的的数,如果可以被4整除,可以的,但是如果不能,那么就计算最小的可以被数.例如7不可以被4整除,那么计算结果是8就可以了.

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

这里我们就可以解决下面的问题.

#include <stdarg.h>

int FindMax(int num, ...)
{
	va_list arg;
	va_start(arg, num);

	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);
	return max;
}

int main()
{
	int max = FindMax(5, 'a', 'b', 'c', 'd', 'e');
	printf("max = %d\n", max);
	system("pause");
	return 0;
}

image-20230930122955339

我们知道,寻找我们参数的时候,我们需要知道两个最关键在的元素.

  • 参数地址
  • 参数的大小

其中参数地址我们已经使用宏可以很容易的找到,我们也可以接受每一个参数的大小编译器也可以帮助我们做好,但是这里存在一个问题,我们的传入的是char,在计算的时候确实int,这里给我们很大的疑惑.其实我们这里只需要看一下我们的的汇编代码我们就可以明白了.在VS2013开发环境下看一下.

image-20230930131709022

看一下他的这条指令的解释,本质上可以理解为整型提升.

MOV BL,80H
MOVSX AX,BL
运行完以上汇编语句之后,AX的值为FF80H。由于BL为80H=1000 0000,最高位也即符号位为1,在进行带符号扩展时,其扩展的高8位均为1,故赋值AX为1111 1111 1000 0000,即AX=FF80H。

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

  1. 实际传入的参数如果是char,short,float,编译器在编译的时候,会自动进行提升(通过查看汇编,我们都能看到)
  2. 函数内部使用的时候,根据类型提取数据,更多的是通过int或者double来进行

va_arg

这里我们还要明白一件事情,我们找到了实际的参数的时候,指针需要一步步的遍历的我们的所有的参数,也就是这里我们需要知道两个内容

  • 拿到一个参数的完成的内容
  • 移动指针到下一个参数那里
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

这里我们可以很容易的发现,在VS2013中,这里我们先移动的指针,让后让指针回指到原来的位置,这样就可以得到一整个的元素的内容,并且指针也被更新了.

计算规则

下面我们学下一下前面我们并没有仔细谈到的两个计算公式.

_ADDRESSOF

这个主要在va_start中使用.

image-20230930130429542

#define va_start _crt_va_start
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

他的作用可以理解找到我们可变参数第一个参数也就是num的地址,然后将ap指针移动到我们的实际参数的元素的位置.

_INTSIZEOF

这个宏才是比较的困难的,上面我们说了找4的倍数.

#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的最小整数倍取整,那么本质是:x=4*m,m是具体几倍。对7来讲,m就是2,对齐的结果就是8而m具体是多少,取决于n是多少.如果n能整除4,那么m就是n/,如果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

搞清楚了满足条件最小是几倍问题,那么,计算一个最小数字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,arg,参数,可变,我们
From: https://blog.51cto.com/byte/7663814

相关文章

  • 简单数学函数(最小公倍数与最大公约数与快速幂)
    最大公约数(\(gcd\)):intgcd(inta,intb){returnb?gcd(b,a%b):a;}最小公倍数(\(lcm\)):intlcm(inta,intb){returna/gcd(a,b)*b;//注意:除数为gcd(a,b)}快速幂:template<typenameA,typenameB,typenameC>Cpow(Ax,By,Cp){ if(x==......
  • 传递函数变换到状态空间
    1.分子为1的传递函数例:\[G(s)=\frac{1}{s^3+a_2s^2+a_1s+a_0}\]首先写成输入输出关系:\[(s^3+a_2s^2+a_1s+a_0)Y(s)=U(s)\]对应的微分方程:\[\dddot{y}(t)+a_2\ddoty(t)+a_1\doty(t)+a_0y(t)=u(t)\\\dddot{y}(t)=-a_2\ddoty(t)-a_1\doty(t)-a_0y(t)+u(t)\\\]令:\[x_1=y......
  • 正弦函数在matlab中实现
    %正弦函数在MATLAB中如何实现%1.sin(45°)注意:参数值需要用“弧度”去定义>>x=sin(45*pi/180);%2.MATLAB中注意:开方-sqrt(x),指数函数-exp(x)>>y=sqrt(2*exp(x+0.5)+1); %3.MATLAB中几元几次方程的写法:2x+3y-z=2%8x+2y+3z=4%45x+3y+9z=23<<A=[2,3,-1;8,2,3;45,3,9];%......
  • 无涯教程-JavaScript - REPLACE函数
    描述REPLACE函数根据您指定的字符数,用不同的文本字符串替换文本字符串的一部分。REPLACEB根据您指定的字节数,用不同的文本字符串替换文本字符串的一部分。REPLACE适用于使用单字节字符集(SBCS)的语言,而REPLACEB适用于使用双字节字符集(DBCS)的语言。您计算机上的默认语言设......
  • ​​pandas.get_dummies()​​ 是一个用于执行独热编码(One-Hot Encoding)的 pandas 函
    pandas.get_dummies()是一个用于执行独热编码(One-HotEncoding)的pandas函数。它用于将分类(或离散)特征转换为模型可以处理的二进制格式,以便更好地在机器学习算法中使用。独热编码将每个不同的类别值转换为一个新的二进制特征列,其中每个列代表一个类别,并且只有一个值为1,其余为0......
  • 莫比乌斯函数
    推荐视频:518筛法求莫比乌斯函数前提知识:莫比乌斯函数点击查看代码#include<bits/stdc++.h>usingnamespacestd;#defineLLlonglongconstintN=1e8+10;intp[N],cnt;intmu[N];//d[i]记录i的约数的和boolvis[N];voidget_mu(intn){//筛法求约数的个数 mu[......
  • 无涯教程-JavaScript - LEN函数
    描述LEN返回文本字符串中的字符数。LENB返回用于表示文本字符串中字符的字节数。仅当将DBCS语言设置为默认语言时,它每个字符计数2个字节。否则,LENB的行为与LEN相同,每个字符计数1个字节。支持DBCS的语言包括日语,中文(简体),中文(繁体)和韩语。语法LEN(text)LENB......
  • 函数基础和函数参数
    第一部分:函数基础 函数的作用意义:1.为了更好地管理代码,可能对应的代码块需要重复多次使用,所以通过一个函数封装起来,便于下次直接调用2.方法实际上是通过函数实现的例1:#type()#内置函数deflis():li=[1,2,3]li.append(4)li.pop(2)#指定删除......
  • 无涯教程-JavaScript - FIND函数
    描述FIND和FINDB在第二个文本字符串中定位一个文本字符串,并从第二个文本字符串的第一个字符返回第一个文本字符串的起始位置的编号。FIND适用于使用单字节字符集(SBCS)的语言,而FINDB适用于使用双字节字符集(DBCS)的语言。您计算机上的默认语言设置会影响返回值,如下所示:......
  • 无涯教程-JavaScript - Exact函数
    描述EXACT函数比较两个文本字符串,如果它们完全相同,则返回TRUE,否则返回FALSE。语法EXACT(text1,text2)争论Argument描述Required/OptionalText1Thefirsttextstring.RequiredText2Thesecondtextstring.RequiredNotesEXACT区分大小写,但忽略格式差异......