C语言进阶可变参数列表

可变参数

可变参数是C语言提供的一种参数可变的机制,咱希望函数带有可变数量的参数,而不是预定义数量的参数。它允许咱定义一个函数,能根据具体的需求接受可变数量的参数,比如这种:

int Max(int num,...)
{
 va_list arg;
 va_start(arg,num);
 int max = va_arg(arg,int);
 for(int i = 1;i<num;i++)
 {
 int sid = va_arg(arg,int);
 }
 if(sid > max)
 {
 max = sid;
 }
 va_end(arg);
 return max;
}
int main()
{
int a = Max(5,1,2,3,4,5);
printf("%d\n",a);
return 0;
}

如上形式Max函数就用到了可变参数,注意!使用可变参数时,Max内首元素 ‘ 5 ’代表元素个数

那么问题来了,如果函数没有形式参数,可以给函数传递吗?答案是可以的,在C语言中,只要发生了函数调用并调用了参数,必定会形成临时变量;所谓临时拷贝(变量)的本质,也就是在栈帧内部形成的(从右向左形成临时拷贝(变量)).

宏观过程

va_list定义了可以访问可变参数部分的变量,他的本质是一个 char 类型指针。va_start 使 b 指向可变参数部分,va_end 是用来完成收尾工作的,本质就是将参数arg置为空,避免野指针。

掐头去尾,我们看看主体部分。首先 arg 指针先让我的数据入栈,我们打开反汇编能看到栈顶 esp 位置,再在内存窗口找到 esp 位置,就会看到这个经典的一幕,倒着入栈连着几个数据入栈是压在一起的,这种结构对我们查找元素就非常友好了。

宏观的框架就是我们传入的变量 num 就代表第一个参数 5,va_start 就是让 arg 原本指向5的 ,再让他指向有效部分,比如 1,根据指向 1 的起始地址, va_start 指向他的可变部分(去掉已指向的有效部分),具体如何实现见下文;最后 va_arg 就是根据类型 int ,从起始地址开始连续读取找到某一个元素,这样最终会把所需要的 max 的值读出来。

原理

可变参数列表对应的函数,最终调用也是函数调用,也要形成栈帧,栈帧形成前,临时变量会先入栈,根据咱之前总结的,参数位置都是相对固定的;在可变参数中 ,如果是短整型,一般都要进行整型提升,比如参数传入的是 char 类型,但实际传出的是 int 类型,这就是我们的 va_arg(arg,int)为什么是 int 而不是 char,所以在 va_arg 中指定了错误的类型,那结果无法预测。

要注意:
1.可变参数必须从头到尾逐个进行访问,如果你访问了几个可变参数后想半途而废,是可以做到的,但如果一开始就想访问中间某个元素的话,哒咩!
2.参数列表中至少有一个命名参数,如果连一个参数都没有,就没办法使用 va_start;
3.这些宏是没办法直接判断实际存在的参数数量的,也无法判断每个参数的类型

格局打开

#define _crt_va_start(ap,v)  (ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)
#define _crt_va_arg(ap,t)  (*(t*)((ap += _INTSIZEOF(t) - _INTSIZEOF(t))
#define _crt_va_start(ap)  (ap = (va_list)0)
)

谈完原理就要谈原理的原理,可变参数的几个宏就给出了他的运作原理,ap 就相当于 arg, v 就相当于变量 num,va_list 相当于 char *,这里 ADDRESS 相当于取地址,所以就是在对 char 指针强转之后,此时就有了一个指针以一字节为单位,指向入栈的第一个有效元素。要想继续指向后面可变部分,就要继续向下移动四个字节,加上他本身大小就能移动到可变部分。

第二个宏也是特别有意思,ap是va_arg(arg,int),t 是我们的类型—— int ,括号里的部分:(ap += _INTSIZEOF(t))其中 INTSIZEOF 计算了int 的大小,这里让 ap 先 += 四个字节,就让 ap 直接指向了下一个元素的位置,后面再减去 int 的大小让他又回到了第一个元素

注意减的过程并没有赋给 ap,ap指向的是 2,而整个表达式指向的是 4,(t) 将这个 char 类型指针强转成 int 类型指针再解引用,通过强制转换,提取出符合类型大小的数据。整个过程就是把第一个元素分离出来了。这个设计可谓非常优秀,不仅指针下移了,元素也访问了,属实美哉。

end宏很好理解,ap = 0了再强转成 char* ,他的实际意义就是将指针归0,避免野指针。

四字节对齐

INTSIZEOF 是如何实现的?我们将 INTSIZEOF 转到定义,下面这段宏在函数内部就开始进行使用了为什么还要进行四字节对齐呢?因为从首元素到第二个元素中间的空间是可以访问的,我不限制大小就有可能访问不到第二个元素,所以在形参被运用时除了发生整型提升还有就是四字节对齐。

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

比如我是个 char 类型,sizeof(char)+sizeof(4)-1 &~ (sizeof(4)-1)就是 4 &~ 3,0000……0100 & 1111……1100 = 4 , 如此就能实现以最小的方式向上四字节取整,完成四字节对齐。从可读性上讲,这是真的麻烦,我们其实直接写成(n+4-1)& -(4-1)也无妨,这种简洁版不香吗是吧。

今天就先到这里,摸了家人们,更多关于C语言可变参数列表的资料请关注我们其它相关文章!

(0)

相关推荐

  • C语言如何实现可变参数详解

    目录 可变参数 实现 代码 分析 关键语句 为什么 内存地址 难点 优化 总结 可变参数 可变参数是指函数的参数的数据类型和数量都是不固定的. printf函数的参数就是可变的.这个函数的原型是:int printf(const char *format, ...). 用一段代码演示printf的用法. // code-A #include <stdio.h> int main(int argc, char **argv) { printf("a is %d, str is %s,

  • C语言可变长的参数列表详解

    C语言可变长的参数列表 C语言可创建接收参数个数不确定的函数.如常用的标准库函数printf就是一个接收参数个数可变的函数.函数printf至少要接收一个字符串作为它的第一个实参.但事实上,printf还能够接收任意数目的其他实参.printf的函数原型是: int printf(const char *format, ...); 其中的省略号(…)表示这个函数可以接收可变数目的各种类型的实参. 需要注意:这个省略号必须放在形参列表的末尾. 可变参数头文件<stdarg.h>中的宏和定义,为创

  • C语言的可变参数函数实现详解

    目录 1.简介 2.简单的使用方式 总结 1.简介 今天看到一个有趣的东西C语言的可变参数函数 众所周知,C语言的函数不能重载,那么你printf和scanf是怎么可以输入多个参数的 例如查看到的printf的定义为 printf(const char *_Restrict, ...); 这称为可变参数函数.这种函数需要固定数量的强制参数,后面是数量可变的可选参数 这种函数必须至少有一个强制参数.可选参数的类型可以变化.可选参数的数量由强制参数的值决定,或由用来定义可选参数列表的特殊值决定. C

  • C语言中编写可变参数函数

    通过stdarg.h头文件为函数提供了定义可变参数列表的能力.声明一个可变参数的函数类似: void f1(int n,...); 其中n表示参数列表个数,而用省略号来表示未知参数列表.stdarg.h中提供了一个va_list类型,用于存放参数.一个大概的使用过程类似: void f1(int n,...) { va_list ap; va_start(ap,n); //初始化参数列表 double first=va_arg(ap,double); //取第一个参数 int second=va

  • C语言中可变参数的使用方法示例

    前言 在C语言程序编写中我们使用最多的函数一定包括printf以及很多类似的变形体.这个函数包含在C库函数中,定义为 int printf( const char* format, ...); 除了一个格式化字符串之外还可以输入多个可变参量,如: printf("%d",i); printf("%s",s); printf("the number is %d ,string is:%s", i, s); 格式化字符串的判断本章暂且不论,下面分析一

  • C语言进阶可变参数列表

    可变参数 可变参数是C语言提供的一种参数可变的机制,咱希望函数带有可变数量的参数,而不是预定义数量的参数.它允许咱定义一个函数,能根据具体的需求接受可变数量的参数,比如这种: int Max(int num,...) { va_list arg; va_start(arg,num); int max = va_arg(arg,int); for(int i = 1;i<num;i++) { int sid = va_arg(arg,int); } if(sid > max) { max = s

  • C语言可变参数列表的用法与深度剖析

    目录 前言 一.可变参数列表是什么? 二.怎么用可变参数列表 三.对于宏的深度剖析 隐式类型转换 对两个函数的重新认知 总结 前言 可变参数列表,使用起来像是数组,学习过函数栈帧的话可以发现实际上他也就是在栈区定义的一块空间当中连续访问,不过他不支持直接在中间部分访问. 声明: 以下所有测试都是在x86,vs2013下完成的. 一.可变参数列表是什么? 在我们初始C语言的第一节课的时候我们就已经接触了可变参数列表,在printf的过程当中我们通常可以传递大量要打印的参数,但是我们却不知道他是如何

  • Java可变参数列表详解

    Java可变参数列表详解 1.接受的传入参数情况: 如public void test(String ...args){...} 1)不使用参数,如test() 2)使用一个或多个参数,如test("1"); test("1","2"); 3)使用数组 test(new String[]{"1","2"}); 2.方法内部访问参数: 在test方法内部,我们可以像使用数组的访问方式一样来访问参数args.如

  • Scala可变参数列表,命名参数和参数缺省详解

    重复参数 Scala在定义函数时允许指定最后一个参数可以重复(变长参数),从而允许函数调用者使用变长参数列表来调用该函数,Scala中使用"*"来指明该参数为重复参数.例如: scala> def echo (args: String *) = | for (arg <- args) println(arg) echo: (args: String*)Unit scala> echo() scala> echo ("One") One sca

  • 用c语言根据可变参数合成字符串的实现代码

    写代码时, 经常需要根据参数值得到一特定的字符串. 每次都调用vsprintf, malloc很烦. 以下是一个实现了此功能的接口. 复制代码 代码如下: #include <stdio.h>#include <stdlib.h>#include <string.h>#include <stdarg.h> char *xm_vsprintf_ex(int len, char *fmt, ... ); int main(int argc, char *argv

  • C语言可变参数函数详解示例

    先看代码 复制代码 代码如下: printf("hello,world!");其参数个数为1个.printf("a=%d,b=%s,c=%c",a,b,c);其参数个数为4个. 如何编写可变参数函数呢?我们首先来看看printf函数原型是如何定义的.在linux下,输入man 3 printf,可以看到prinf函数原型如下: 复制代码 代码如下: SYNOPSIS#include <stdio.h>int printf(const char *form

随机推荐