不定参数当年做为C/C++语言一个特长被很多人推崇,但是实际上这种技术并没有应用很多。除了格式化输出之外,我实在没看到多少应用。主要原因是这种技术比较麻烦,副作用也比较多,而一般情况下重载函数也足以替换它。尽管如此,既然大家对它比较感兴趣,我就简单总结一下它的使用和需要注意的常见问题。
原理
刚学C语言的时候,一般人都会首先接触printf函数。通过这个函数,你可以打印不定个数的变量到屏幕,如:
printf("%d", 3);
printf("%d,%d",3,4);
上述代码看似简单,实际上却需要我们解决许多问题。在我们设计printf的时候,我们是不知道到底会传入几个参数的。在这种未知的情况下,我们需要解决下面几个问题:
怎么告诉printf我们会传入几个参数
printf怎么去访问这些参数
函数调用完成后,系统怎么把参数从传递用的堆栈中释放
为了解决这些问题,我们首先要解释cdecl调用约定(参见论调用约定),所有使用不定参数的函数必须是使用cdecl(全局函数)或者this call(类成员函数)调用约定。该约定对于参数传递规定如下:
参数从右向左入栈(也就是如果你调用f(a,b,c),则c先入栈,然后是b,最后是a入栈)
调用者负责清理堆栈
其中第二点直接解决了前面三个问题中的第三个问题。我们来详细说说其他两个问题。
确定参数的个数
在一个函数中,一般有如下prelog代码:
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,48h
其中,eb指向保存旧的ebp的堆栈内存的下一个字的地址,ebp+8指向eip地址,ebp+12则指向函数调用的第一个参数,而ebp和esp之间是用于临时变量(也就是堆栈变量)的空间。
注意,由于上述prelog代码的存在,我们很容易通过ebp得到第一个参数的地址,对于不定参数列表之前的类型固定的参数,我们也可以根据类型信息得到其实际的位置(例如,第一个参数的位置偏移第一个参数的大小,就是第二个参数的地址)。
注意不定参数函数有个限制,就是不定参数的列表必须在整个函数的参数列表的最后。我们不可以定义如下的函数:
void func(int a, ..., int c)
所有类型固定的参数都必须出现在参数列表的开始。这样根据前面的论述,我们就可以得到所有类型固定的参数。
在设计具有不定参数列表的函数的时候,我们有两种方法来确定到底多少参数会被传递进来。
方法1是在类型固定的参数中指明后面有多少个参数以及他们的类型。printf就是采用的这种方法,它的format参数指明后面每个参数的类型。
方法2是指定一个结束参数。这种情况一般是不定参数拥有同样的类型,我们可以指定一个特定的值来表示参数列表结束。下面这个sum函数就是一个例子:
int sumi(int c, ...)
...{
va_list ap;
va_start(ap,c);
int i;
int sum = c;
c = va_arg(ap,int);
while(0!=c)
...{
sum = sum+c;
c = va_arg(ap,int);
}
return sum;
}
使用这个函数的代码为:
int main(int argc, char* argv[])
...{
int i=sumi(1,2,3,4,5,6,7,8,9,0);
return 0;
}
访问各个参数
其实前文已经告诉我们怎么去访问不定参数。va_start和va_arg函数可以被结合起来用于依次访问每个函数,他们实际上都是宏函数。
在vc6,va_start函数定义为:
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
其中_INTSIZEOF(n)计算比n大的sizeof(int)的最小倍数,如果n=101,则_INTSIZEOF(n)为104。
va_start执行完毕后,ap指向变量v后第一个4字节对齐的地址。例如,v的地址为0x123456, v的大小为13,则v后面的下一个与字边界对齐的地址为0x123456+0x0D=0x123463再调整为与4字节对齐的下一个地址,也就是0x123464.
va_arg函数定义为:
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
分析与va_start一样,它的结果是使ap指向当前变量的下一个变量。
这样,我们只要在开始时使用va_start把不定参数列表赋值给ap,然后依次用va_arg获得不同参数即可。
潜在问题
使用不定参数列表,有两个问题特别需要注意。
问题1的理解相对简单:我们在重载一个函数的时候,不能依赖不定参数列表部分对函数进行区分。
假定我们定义两个重载函数如下:
int func(int a, int b, ...)
int func(int a, int b, float c);
则上述函数会导致编译器不知道怎么去解释func(1,2, 3.3),因为当第三个参数为浮点数时,两个实现都可以满足匹配要求。一般情况,个人建议对于不定参数函数不要去做重载。
另外一个问题是关于类型问题。绝大多数情况下,C和C++的变量都是强类型的,而不定参数列表属于一个特例。
当我们调用va_arg的时候,我们指明下一个参数的类型,而在执行的时候,va_arg正是根据这个信息在堆栈上来找到对应的参数的。如果我们需要的类型和真实传递进来的参数完全一致时自然没有问题,但是假如类型不一样,则会有大麻烦。
假如上面的的sumi函数,我们用下面方法调用:
int sum = sumi(1, 2.2, 3, 0)
注意第二个参数我们传入了一个double类型的2.2,我们希望sumi在做加法时可以做隐式类型转换,转换为int进行计算。但是实际情况时,当我们分析到这个参数时,调用的是:
c=va_arg(ap,int)
根据前文va_arg的定义,这个宏被翻译成:
#define va_arg(ap,t) ( *(int *)((ap += _INTSIZEOF(int)) - _INTSIZEOF(int)) )
如果后面的+=计算出正确的地址,最后就变成
*(int*)addr
如果希望能得到正确的整数值,必须要求addr所在的地址是一个真实的int类型。但是当我们传入double时,实际上其内存布局和int完全不同,因此我们得不到需要的整数。感兴趣的朋友可以用下面简单的代码做测试:
double a;
a=1.1;
int b = *(int*) & a;
因此,当我们调用有不定参数列表的函数时,不要期望系统做隐式类型转换,系统不会做这种检查或者转换,你给的参数类型必须严格和你希望的值一样。
相关推荐
C专家编程--指针和数组(三) 值传递 指针传递 引用传递
http://blog.youkuaiyun.com/yusiguyuan/article/details/12357381 http://blog.youkuaiyun.com/yusiguyuan/article/details/12357457 而关于值传递,指针传递,引用传递这几个方面还会存在误区, 所有我觉的有必要在这里也说明一下~ 下文会通过例子详细说明哦 值传递: 形参是实参的
C++ 函数中的不定参数
.定义 所谓不定参数,指的就是在函数的定义中
【PyTorch】TypeError: argument 0 is not a Variable
TypeError: argument 0 is not a Variable 问题定位: Variables and tensors:需要将输入数据转换为 Variable 类型。 解决方案: 升级 PyTorch 版本:In the current pytorch version (0.4.0) Variables and tensors are merged, so that sh...
C语言和C++中不定参数解析
函数传参传几个呢?两个太少,三个太多,我传一坤个,不定参数来帮你
西门子828D/840DSL数控机床产量数据采集实战:一个C#程序员的踩坑与填坑记录
本文详细记录了C#程序员在西门子828D/840DSL数控机床产量数据采集实战中的经验与教训。从硬件连接到软件配置,再到数据读取与异常处理,文章分享了工业现场编程的特殊挑战和解决方案,特别是针对数控系统的TCP/IP通信、PLC变量地址映射及数据解析等关键技术点。
基于ZYNQ7020芯片(黑金开发板AX7020)之AXI-FULL DDR的PL与PS交互操作。
在该工程中我们使用了PS端的串口接收功能,PS端串口接收和发送分别有一个64字节的FIFO,但是因为串口接收阈值设置为16,所以固定接收16个字节数据后才会发生中断。此测试工程得到以前知识的总结和各大网友的支持,只希望可以为学习的你更进一步,大家一起学习才是真的开心。加油,国产芯片之路。
Workflow 4.0中的Variable和Argument
还是那句老话,Workflow 4.0和Workflow 3.0/3.5相比有很大的不同,其中之一就是Activity间数据交换方式的变化。在3.0/3.5中,我们使用DependenceProperty,使用绑定的方式在不同的Property建立关联,由此实现数据的交换。这种方式的确很灵活,但是也带来不少弊病。比如所有的数据都存在同一个域中导致性能的下降,同时DependenceProper
嵌入式Linux Qt应用程序卡死问题分析记录
根据应用和系统层面的分析,软件功能异常和卡死现象的原因是系统控制台/dev/console阻塞导致依赖其的相关应用和命令异常。系统控制台/dev/console阻塞的原因还未确定,最近的软件版本和其他测试同事未出现该问题,所以很大可能是串口线的问题,待后续有进展了继续更新。
variable argument parameter的区别
今天看书,发现ARG是argument 的缩写,意思是参数于是想到parameter也是参数,这两个有啥区别咧?于是去问度娘...结果查出这么个东西----------------------------------------------------------------------------------variable是变量 parameter是参数argu
WPF用户控件间属性传递的艺术
本文详细介绍了WPF中父控件向子控件传递属性的方法。针对常见的绑定失效问题,重点讲解了使用RelativeSource绑定技术,通过指定AncestorType确保正确访问父控件属性。文章通过实例演示了从ParentModel传递属性到ChildUserControl依赖属性的完整实现过程,包括模型定义、XAML结构设计和代码实现。这种方法有效解决了数据上下文混淆问题,为WPF开发中的控件交互提供了可靠解决方案,有助于提升应用的灵活性和可维护性。
EPO: Hierarchical LLM Agents with Environment PreferenceOptimization
长期决策和规划任务依然是基于大型语言模型(LLM)的智能体面临的一大挑战(Valmeekam 等,2023;Liu 等,2023;Silver 等,2024)。这些任务需要智能体进行多步骤的规划,同时保持行为的一致性和目标导向,这对于通常被设计用于即时和局部预测的大型语言模型来说颇为困难。此外,针对具身智能体的大型语言模型微调面临的一个关键难题是大规模标注数据的需求(Reed 等,2022)。
variable argument list
variable argument:#define _ADDRESSOF(v) ( &reinterpret_cast(v) )#elif defined(_M_IX86)//Interge promotions#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )//v
Linux----Ubuntu系统官网下载iso镜像文件
Linux----Ubuntu系统官网下载iso镜像文件
Java Variable Argument Lists
case 1: https://github.com/wangbingfeng/OnJava8-Examples/blob/master/housekeeping/VarArgs.java // housekeeping/VarArgs.java // (c)2017 MindView LLC: see Copyright.txt // We make no guarantees that t...
在 Windows 11 下运行 OminiParse V2,详细教程【含问题解决细节】
OmniParser V2 是微软推出的 GUI 智能体解析工具,可将屏幕截图转换为结构化元素,帮助 LLM 理解和操作界面。它在检测小图标和推理速度上显著提升,延迟降低 60%,并支持多种 LLM。
温度PID控制PLC程序
温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值
C#游长变元表(variable-length argument list)
using System; namespace HelloWorld { class Program { /* * 游长变元表(variable-length argument list) * 使程序员能在创建方法时不指定变元的个数。 * 将一维数组类型变元前面加上关键字params,表示这个方法将接收可变数量
复分析 阿尔福斯(Ahlfors) 第三版答案 英文
答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案
Java:Variable argument lists
1 class A { 2 } 3 4 public class NewVarArgs { 5 6 static void printArray(Object... args) { 7 for (Object obj : args) { 8 System.out.print(obj + " ...
文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况
主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。
Statistics and Machine Learning Toolbox
Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具
variable argument print function
variable argument print function
pytorch:argument 0 is not a Variable
运行sun_test.py时出现如下错误: 出现此错误的原因是把Tensor类型的数据输入训练的模型,需要将输入数据转换为Variable类型,然后可以代码可以正常运行。...
c variable-length argument lists
a simple program demostrate c variable-length argument lists, and how to refer variable-length argument in c macro. #include <stdio.h> #include <stdlib.h> #include <stdarg.h> //fm...
variable-length argument lists
The best example of variable-length argument lists function is printf() the proper declaration for printf() is int printf(char * format,
Variable argument lists in Cocoa
Variable argument lists in Cocoa This week I'll talk about methods that take variable numbers of arguments, also known as variadic methods. I'll show you the Objective-C syntax and implementation,
WF Activity得到Variable and Argument
publicsealedclassCustomActivity1:NativeActivity{protectedoverridevoidCancel(NativeActivityContextcontext){Console.Write("Cancel");base.Cancel(cont...
pass variable-argument list
IntroductionIve been looking for solution of passing variable-argument list from my va-function to another one, like TRACE for example. All solutions I saw were about using special functions that t
Variable-length argument tuples
write a function called sum_all that takes any number of arguments and returns their sum. def sum_all(*nu): cnt = 0.0 for i in nu: cnt += i return cnt print(sum_all(1, 3, 5.5)) print(sum_all(2.2, 3.3, 9, 555))
5190



被折叠的 条评论
为什么被折叠?



