深度探索va_start、va_arg、va_end
采用C语言编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数。但在某些情况下希望函数的参数个数可以根据需要确定。典型的例子有大家熟悉的函数printf()、scanf()和系统调用execl()等。那么它们是怎样实现的呢?
C编译器通常提供了一系列处理这种情况的宏,以屏蔽不同的硬件平台造成的差异,增加程序的可移植性。这些宏包括va_start、va_arg和va_end等。在讲解以上宏之前我们先了解一下调用函数时传入参数的处理过程。
一、函数传入参数过程
一个函数包括函数名、传入参数、返回参数以及函数体,函数体编译后的二进制代码储存在程序代码区。当用户调用某个函数时,系统会通过函数名(C++中会涉及到mangled命名处理)查找函数体入口指针并压入栈中,之后将传入参数以从右至左的顺序压入栈中(栈空间是往低地址方向增长的,也即栈底对应高地址,栈顶对应低地址),示例如下:
#include <iostream>
using namespace std;
void fun(int a, ...)
{
int *temp = &a;
temp++;
for (int i = 0; i < a; ++i) { cout<< *temp << endl;
temp++;
}
}
int main()
{
int a = 1;
int b = 2;
int c = 3;
int d = 4;
fun(4, a, b, c, d);
system("pause");
return 0;
}
// Output:
// 1
// 2
// 3
// 4
二、va_start、va_arg和va_end宏定义
接着我们再来看看va_start、va_arg和va_end等宏的具体定义。
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) // 此句宏的作用是将类型n的大小向上取成4的倍数,如n为char型的话结果即为4
#ifdef __cplusplus
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) ) // vs2015中此句高亮,作用是将v的地址重新解释成char*型
#else
#define _ADDRESSOF(v) ( &(v) )
#endif
#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_end(ap) ( ap = (va_list)0 )
#ifndef _VA_LIST_DEFINED
#ifdef _M_CEE_PURE
typedef System::ArgIterator va_list;
#else
typedefchar * va_list; // vs2015中此句高亮
#endif /* _M_CEE_PURE */
#define _VA_LIST_DEFINED
#endif
// 以上宏定义出现在vadef.h,通过stdio.h即可使用
#define va_start _crt_va_start #define va_arg _crt_va_arg #define va_end _crt_va_end // 以上宏定义出现在stdarg.h中,若要使用则需加上 #include <stdarg.h>
三、va_start、va_arg和va_end使用示例
容易看出,以上宏定义主要涉及地址操作,va_start获取第二个传入参数的地址给va_list类型变量(假设为arg_ptr),va_arg用于获取当前参数值并将指针arg_ptr往后移,va_end则是将arg_ptr置为空。va_start、va_arg、va_end具体用法示例如下:
#include <stdio.h>
#include<stdarg.h>
int fun(int x, int y) {
return x - y;
}
int fun(int count, ...) {
va_list arg_ptr; // 等同于 char *arg_ptr;
int nArgValue = count;
int nArgCout = 0;
va_start(arg_ptr, count); // 使arg_ptr指向第二个参数的地址
printf("The 1 th arg: %d\n", nArgValue); // 输出第一个参数的值
int sum = 0;
for (int i = 0; i < count; i++)
{
++nArgCout;
nArgValue= va_arg(arg_ptr, int);// 将arg_ptr所指参数返回成int并移动arg_ptr使其指向后一个参数,这里假设传入参数均是int型
printf("The %d th arg: %d\n", i+2, nArgValue); // 输出各参数的值
sum += nArgValue;
}
va_end(arg_ptr); // 将arg_ptr置为空
return sum;
}
int main() {
cout<< fun(0) << endl;
cout<< fun(1, 1) << endl; // 优先匹配到函数int fun(int, int), 输出0
cout << fun(2, 3, 4) << endl;
system("pause");
return 0;
}
// Output: // 0 // 0 // The 1 th arg: 2 // The 2 th arg: 3 // The 3 th arg: 4 // 7
注意:以上宏操作并不提供参数个数获取操作,这需要用户在函数中获取,如第二个fun函数使用count指明个数,printf通过解析第一个传入参数来确定参数个数与类型等。
深度探索va_start、va_arg、va_end相关推荐
- va_list/va_start/va_arg/va_end深入分析
va_list/va_start/va_arg/va_end这几个宏,都是用于函数的可变参数的. 我们来看看在vs2008中,它们是怎么定义的: 1: ///stdarg.h 2: #define v ...
- 变长参数va_list va_start va_arg va_end
对于int printf(const char *format, ...);这种变长参数,需要使用va_list va_start va_end va_arg来访问参数. 下面是一个tutorials ...
- C语言使用函数参数传递中的省略号:va_list, va_start, va_arg, va_end
首先要处理这种省略号的参数的话,需要包含头文件#include <stdarg.h>,然后利用下面的函数对"..."省略号变量进行处理. va_list arg; ty ...
- 如何获取函数的变长参数(va_list, va_start, va_arg, va_end)
最近在花时间研读C++. 函数这章讲到了函数的变长参数(ellipsis...),但是primer中讲得比较浅,提到了怎么声明怎么调用,但是没有写明在函数内部是如何获取变长的参数的. 1)省略号(el ...
- 可变参数的函数,va_start(), va_arg(), va_end()
头文件 stdarg:类型va_list:宏va_start:宏va_arg:宏va_end 1 #include <stdio.h>2 #include <stdarg.h> ...
- Va_list Va_start va_arg Va_end 的用法
首先我们先看看它的头文件是怎么描述的 stdarg.h #pragma once#ifndef _INC_STDARG #define _INC_STDARG#if !defined(_WIN32) ...
- 可变参数:va_list(),va_start(),va_arg(),va_end() 详细解析
目录 1.含义: 2.使用: 3.连续打印出自定义格式的文字: 1.含义: (1)va_list是C语言中的一个宏定义,用于表示一个变长参数列表.它是一个指向变长参数列表的指针,可以通过宏va_sta ...
- stdarg.h中三个宏va_start ,va_arg\va_end及vsprintf 的应用
我们在C语言编程中会遇到一些参数个数可变的函数,例如printf() 这个函数,它的定义是这样的: int printf( const char* format, ...); 它除了有一个参数form ...
- va_list、va_start和va_end使用
我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的,由于1.硬件平台的不同 2.编译器的不同,所以定义的宏也有所不同. 在ANSI C中,这些宏的定义位于stdar ...
最新文章
- 使用alipaySDK编译时找不到openssl/asn1.h文件的解决办法(初探)
- R语言quantmod包
- DotNetty 实现 Modbus TCP 系列 (三) Codecs Handler
- java开源服务框架_Java框架服务
- 《C和指针》——字符数组和字符串常量的区别
- Hadoop系列之FieldSelectionMapReduce用法
- Alexa 世界网站排名研究
- 《软件工程(第4版?修订版)》—第2章2.9节本章对研究人员的意义
- python能做什么项目-Python 的练手项目有哪些值得推荐?
- Java Web中界面之间传值的实现方法
- qi无线充电协议_iOS 13.1封杀第三方无线快充:疑似苹果无线充电私有协议来了...
- nodejs+vue 智能餐厅菜品厨位分配管理系统
- 无源码程序反编译修改文字
- 课程设计实验--火车票座位分配
- 树状数组入门——以洛谷3374为例
- mysqlit根据稀有值随机选择_三中锋教练来过了!实况足球20赛季DP7.0后新增稀有阵型top10...
- 上海青浦区大众驾校(科目二·自动挡)真实考场操作全程
- 系统调用中断(EINTR)与SIGCHLD信号的处理
- 【回首2022,展望2023,兔年你好!】
- 添加集控程序的守护进程一般操作【Linux,CentOS7.5】
热门文章
- why do you need that a awesome linkedin profile
- a good resource gathering system from sustech
- where is lingang city in shanghai?
- iPad导入Mac:非常快!一气呵成,直接去photo里面选择,之后左上角倒出就好,颠覆之前windows上面的认知!
- 学到了关于服务器磁盘阵列
- 【转】6 个技巧,提升 C++11 的 vector 性能
- 在eclipse及myEclipse下安装插件之方法
- 欧拉函数的相关应用 noj欧拉函数求和+noj 最大公约数求和
- 你真的会使用assert吗?
- 【Android】Android 8种对话框(Dialog)