不定参数的应用

【c++】如何给函数传递不定数量的参数 有时候我们给一个函数传递的参数数量是不确定的,比如如果想打印多个字符串,数量不定,那么可以这么写: #include <stdarg.h> #include <iostream> using namespace std; void printStrings(int argNum, ...) { va_list args; va_start(args, argNum); for (int i = 0; i < argNum; i++) { 阅读详情

 

不定参数的应用

不定参数当年做为C/C++语言一个特长被很多人推崇,但是实际上这种技术并没有应用很多。除了格式化输出之外,我实在没看到多少应用。主要原因是这种技术比较麻烦,副作用也比较多,而一般情况下重载函数也足以替换它。尽管如此,既然大家对它比较感兴趣,我就简单总结一下它的使用和需要注意的常见问题。

原理

刚学C语言的时候,一般人都会首先接触printf函数。通过这个函数,你可以打印不定个数的变量到屏幕,如:

printf("%d", 3);
printf("%d,%d",3,4);

 上述代码看似简单,实际上却需要我们解决许多问题。在我们设计printf的时候,我们是不知道到底会传入几个参数的。在这种未知的情况下,我们需要解决下面几个问题:

  1. 怎么告诉printf我们会传入几个参数
  2. printf怎么去访问这些参数
  3. 函数调用完成后,系统怎么把参数从传递用的堆栈中释放

为了解决这些问题,我们首先要解释cdecl调用约定(参见论调用约定),所有使用不定参数的函数必须是使用cdecl(全局函数)或者this call(类成员函数)调用约定。该约定对于参数传递规定如下:

  1. 参数从右向左入栈(也就是如果你调用f(a,b,c),则c先入栈,然后是b,最后是a入栈)
  2. 调用者负责清理堆栈

其中第二点直接解决了前面三个问题中的第三个问题。我们来详细说说其他两个问题。

确定参数的个数

在一个函数中,一般有如下prolog代码:

00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,48h
执行上述代码之后,func(a,b,c)函数所处的堆栈上下文就变成如下布局:

堆栈布局

其中,ebp指向保存旧的ebp的堆栈内存的下一个字的地址,ebp+8指向eip地址,ebp+12则指向函数调用的第一个参数,而ebp和esp之间是用于临时变量(也就是堆栈变量)的空间。

注意,由于上述prolog代码的存在,我们很容易通过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 而关于值传递,指针传递,引用传递这几个方面还会存在误区, 所有我觉的有必要在这里也说明一下~ 下文会通过例子详细说明哦 值传递: 形参是实参的 阅读详情

相关推荐

【实战指南】OpenClaw 使用 Bird Skill 接入 X(Twitter)的终极省心方案:告别每次都要手动喂 Cookie 的痛苦

OpenClaw 使用 Bird Skill 接入 X(Twitter)的终极省心方案:告别每次都要手动喂 Cookie 的痛苦

hampeter的博客 1801

C++ 函数中的不定参数

.定义 所谓不定参数,指的就是在函数的定义中

Vergesson 1万+

SFF-8654接口引脚定义全解析:从SAS到U.2的高速硬件连接核心

在高速数据存储与传输领域,接口标准是连接主机与存储设备、确保信号完整性与协议互通性的物理基础。其核心原理在于通过精确定义的物理引脚和电气特性,承载不同的高速串行通信协议,如SAS和PCIe。这类高密度连接器的技术价值在于以紧凑的形态支持多协议兼容与高速率传输,满足了企业级存储、高性能计算对带宽和可靠性的苛刻要求。典型的应用场景包括服务器硬盘背板连接、NVMe U.2固态硬盘直连以及外部存储扩展。本文聚焦于SFF-8654(亦称Mini SAS HD)这一关键接口,深入剖析其引脚定义、信号分区(如高速数据通道

weixin_29188043的博客 710

C语言和C++中不定参数解析

函数传参传几个呢?两个太少,三个太多,我传一坤个,不定参数来帮你

wzh18907434168的博客 2886

AgentVerse实战:构建多智能体协作系统的关键技术与应用场景

本文深入探讨了AgentVerse框架在构建多智能体协作系统中的关键技术及应用场景。通过分析智能体通信协议、环境交互机制和任务分解算法,展示了如何利用大型语言模型(LLM)实现智能体间的专业化分工与动态协作。文章还提供了智能软件开发团队和智能客服系统等典型应用案例,以及部署优化实践,为开发者构建高效的多智能体系统提供实用指导。

weixin_29254029的博客 361

李航《统计学习方法》课后习题答案(第2版)

李航《统计学习方法》课后习题答案(第2版) 章节 链接 第1章 统计学习及监督学习概论 点击进入 第2章 感知机 点击进入 第3章 k近邻法 点击进入 第4章 朴素贝叶斯法 点击进入 第5章 决策树 点击进入 第6章 逻辑斯谛回归与最大熵 点击进入 第7章 支持向量机 点击进入 第8章 提升方法 点击进入 第9章 EM算法及其推广 点击进入 第10章 隐马尔可夫模型 点击进入 第11章 条件随机场 点击进入     也可进入我的个人主页查看分类专栏

weixin_43646592的博客 2万+

不定参数应用

不定参数的应用 不定参数当年做为C/C++语言一个特长被很多人推崇,但是实际上这种技术并没有应用很多。除了格式化输出之外,我实在没看到多少应用。主要原因是这种技术比较麻烦,副作用也比较多,而一般情况下重载函数也足以替换它。尽管如此,既然大家对它比较感兴趣,我就简单总结一下它的使用和需要注意的常见问题。 原理 刚学C语言的时候,一般人都会首先接触printf函数。通过这个函数,你可以打印不定个

KUANGPEIYU的专栏 578

Unity Hololens2开发|(一)环境配置

开发过程中预览和调试应用:可以在电脑上的 Unity 编辑器中以播放模式本地运行应用,并将体验流式传输到 HoloLens2。将来自 HoloLens2 的输入(视线、手势、语音和空间映射)发送到电脑,在其中内容以虚拟沉浸式视图渲染。在HoloLens2设备中,打开Settings面板,依次选择Update & Security > For Developers,打开“Developer Mode”。在开发计算机中打开控制面板,依次选择“更新和安全> 开发者选项”,打开“开发人员模式”;

游戏开发小Y的博客 14万+

C语言不定参数的作用,不定参数在C语言中的应用实例

不定参数在C语言中的应用实例:不定参数当年做为C/C++语言一个特长被很多人推崇,但是实际上这种技术并没有应用很多。除了格式化输出之外,我实在没看到多少应用。主要原因是这种技术比较麻烦,副作用也比较多,而一般情况下重载函数也足以替换它。尽管如此,既然大家对它比较感兴趣,我就简单总结一下它的使用和需要注意的常见问题。刚学C语言的时候,一般人都会首先接触printf函数。通过这个函数,你可以打印不定个...

weixin_39559523的博客 271

第3讲:单端传输线的时域TDR仿真(基于实战的第一次仿真视角切换)

本文为CST仿真系列教程的第3讲,主要介绍传输线阻抗(TDR)的两种仿真方法:基于S参数的频域计算和基于高斯脉冲的时域计算。文章详细演示了在CST PCB Studio中修改电容参数、设置TDR仿真参数(包括截止时间和采样点数)的操作步骤,并通过对比实验展示TVS寄生电容对阻抗曲线的影响。最后将两种TDR方法的结果进行对比,验证了两种方法的一致性。本教程适合硬件工程师通过时域波形直观分析传输线阻抗特性,是信号完整性分析的重要技能。

一只豌豆象的博客 227

Android系统去掉长按webview界面弹框中的分享功能

Android 系统去解决长按显示内容,去掉分享功能,不论那个界面,是所有界面都需要满足。如果仅仅去掉系统自带的,那么系统来处理如果客户自定义,部分App自定义的菜单项,不是调用系统的,那么还是需要应用去解决的。之前做过Editext,其实还需要去TextView,最难点的就是WebView中的HTML了 去分享功能了。WebView中涉及到分享功能菜单的界面,/***//***//***//***//***//***//***//***//**

十年饮冰,难凉热血 1019

[c][c++]:不定参数的应用

转载自: http://blog.youkuaiyun.com/arong1234/article/details/2456455 目录(?)[+] 不定参数的应用 原理确定参数的个数访问各个参数潜在问题 不定参数的应用 不定参数当年做为C/C++语言一个特长被很多人推崇,但是实际上这种技术并没有应用很多。除了格式化输出之外,我实在没看到多少应用。主要原因是这种

adairboy的专栏 540

智能体异常行为检测-基于行为序列的异常模式识别技术【附代码】

在多智能体系统(Multi-Agent Systems, MAS)中,智能体之间的行为协调与自主决策是系统稳定运行的关键。然而,当某个智能体因策略偏移、环境变化或潜在攻击而出现**异常行为(Abnormal Behavior)**时,整个系统可能陷入混乱或崩溃。

一键难忘的博客 1143

不定参数在C语言中的应用实例

不定参数在C语言中的应用实例:不定参数当年做为C/C++语言一个特长被很多人推崇,但是实际上这种技术并没有应用很多。除了格式化输出之外,我实在没看到多少应用。主要原因是这种技术比较麻烦,副作用也比较多,而一般情况下重载函数也足以替换它。尽管如此,既然大家对它比较感兴趣,我就简单总结一下它的使用和需要注意的常见问题。   刚学C语言的时候,一般人都会首先接触printf函数。通过这个函数,你可以打

Skylar 701

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

Python中的定长参数和不定长参数:深入理解与应用

与定长参数不同,不定长参数允许你在函数调用时传入任意数量的参数。理解了参数的各种使用方式后,就可以将这些知识应用于更复杂的实际问题中了。定长参数,也就是在函数定义时明确指定的参数,其数量和位置在调用时必须严格匹配。通常是作为参数列表的简写,全称是 “arguments”,意指“参数”。只是一个广泛使用的习惯用法,意味着“多个参数”,但并不是强制的。可以提供额外的灵活性,使得事件处理函数能够接受各种不预期的关键字参数。随后是任意数量的位置参数和关键字参数。其实,这个参数的名称是可以自由选择的。

lph159的博客 2212

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

不定参数函数的原理及应用(转)

概述由于在C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦;即使采用C++,如果参数个数不能确定,也很难采用函数重载.对这种情况,有些人采用指针参数来解决问题.下面就c语言中处理不定参数数目的问题进行讨论. 定义大家先看几宏.在VC++6.0的include有一个stdarg.h头文件,有如下几个宏定义:#define _INTSIZEOF(n)   ((sizeof(n)+si

lsldd的专栏 2854

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

上一篇: Ntldr is missing 怎么办?
下一篇: IT人面试笔试题库及技巧
Jim20
博客等级 码龄18年 34粉丝 24原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值