- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章va_list(),va_start(),va_arg(),va_end() 详细解析由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
(一)写一个简单的可变参数的C函数 。
下面我们来探讨如何写一个简单的可变参数的C函数.写可变参数的C函数要在程序中用到以下这些宏
va在这里是variable-argument(可变参数)的意思. 这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件.下面我们写一个简单的可变参数的函数,该函数至少有一个整数参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值. 。
void simple_va_fun(int i, ...) { va_listarg_ptr; intj=0,
。
va_start(arg_ptr, i); j=va_arg(arg_ptr, int); va_end(arg_ptr); printf("%d%d\n", i, j); return; } 。
我们可以在我们的头文件中这样声明我们的函数: extern void simple_va_fun(int i, ...); 我们在程序中可以这样调用: simple_va_fun(100); simple_va_fun(100,200),
。
从这个函数的实现可以看到,我们使用可变参数应该有以下步骤: 1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针. 2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数. 3)然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个参数是你要返回的参数的类型,这里是int型. 4)最后用va_end宏结束可变参数的获取.然后你就可以在函数里使用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获取各个参数. 。
如果我们用下面三种方法调用的话,都是合法的,但结果却不一样: 1)simple_va_fun(100); 结果是:100 -123456789(会变的值) 2)simple_va_fun(100,200); 结果是:100 200 3)simple_va_fun(100,200,300); 结果是:100 200 我们看到第一种调用有错误,第二种调用正确,第三种调用尽管结果正确,但和我们函数最初的设计有冲突.下面一节我们探讨出现这些结果的原因和可变参数在编译器中是如何处理的. 。
(二)可变参数在编译器中的处理 。
我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的, 由于 1)硬件平台的不同 2)编译器的不同 所以定义的宏也有所不同,下面以VC++中stdarg.h里x86平台的宏定义摘录如下('\'号表示折行)
typedef char * va_list,
。
#define _INTSIZEOF(n) \ ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1)) 。
#define va_start(ap,v) ( ap =(va_list)&v + _INTSIZEOF(v) ) 。
#define va_arg(ap,t) \ ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) 。
#define va_end(ap) ( ap = (va_list)0) 。
定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.C语言的函数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再看va_start的定义,定义为 &v+_INTSIZEOF(v),而&v是固定参数在堆栈的地址,所以我们运行va_start(ap,v)以后,ap指向第一个可变参数在堆 栈的地址,如图
。
高地址 |-----------------------------| |函数返回地址 | |-----------------------------| |....... | |-----------------------------| |第n个参数(第一个可变参数) | |-----------------------------|<--va_start后ap指向 |第n-1个参数(最后一个固定参数) | |-----------------------------|<--&v 低地址 图( 1 ) 。
然后,我们用va_arg()取得类型t的可变参数值,以上例为int型为例,我们看一下va_arg取int型的返回值: j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) ); 首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址(图2).然后用*取得这个地址的内容(参数值)赋给j. 。
高地址 |-----------------------------| |函数返回地址 | |-----------------------------| |....... | |-----------------------------|<--va_arg后ap指向 |第n个参数(第一个可变参数) | |-----------------------------|<--va_start后ap指向 |第n-1个参数(最后一个固定参数) | |-----------------------------|<--&v 低地址 图( 2 ) 。
最后要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg,va_end的描述就是这些了,我们要注意的是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的. 。
(三)可变参数在编程中要注意的问题 。
因为va_start, va_arg,va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型. 有人会问:那么printf中不是实现了智能识别参数吗?那是因为函数printf是从固定参数format字符串来分析出参数的类型,再调用va_arg的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的. 另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.如果simple_va_fun()改为
void simple_va_fun(int i, ...) { va_list arg_ptr; char *s=NULL,
。
va_start(arg_ptr, i); s=va_arg(arg_ptr, char*); va_end(arg_ptr); printf("%d %s\n", i, s); return; } 。
可变参数为char*型,当我们忘记用两个参数来调用该函数时,就会出现core dump(Unix)或者页面非法的错误(window平台).但也有可能不出错,但错误却是难以发现,不利于我们写出高质量的程序. 。
。
以下提一下va系列宏的兼容性. System V Unix把va_start定义为只有一个参数的宏: va_start(va_list arg_ptr); 而ANSI C则定义为: va_start(va_list arg_ptr, prev_param); 如果我们要用system V的定义,应该用vararg.h头文件中所定义的宏,ANSI C的宏跟systemV的宏是不兼容的,我们一般都用ANSI C,所以用ANSI C的定义就够了,也便于程序的移植. 。
最后此篇关于va_list(),va_start(),va_arg(),va_end() 详细解析的文章就讲到这里了,如果你想了解更多关于va_list(),va_start(),va_arg(),va_end() 详细解析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
代码工作完美,但 gcc 和 clang 报告 va_start 上存在问题 stackoverflow 要求我再写一些东西,但我真的不知道是什么 =) int Matrix_cool_input (
在带有可变参数的函数中,我们使用函数 va_start() 初始化一个 va_list 类型的对象,'ap' 为: void va_start(va_list ap, parmN); 我不明白 1.什
根据我对 va_arg 宏的了解,它检索参数列表指向的下一个参数。有什么方法可以选择我想要获取的参数的索引,比如数组索引? 例如,我需要执行一个操作,我需要至少调用 3 次 va_arg 宏,但我希望
我正在阅读 The Linux Programming Interface 一文,他们展示了这个函数来处理错误。在手册页( man stdarg )中它说 va_start必须首先调用以初始化 ap供
据说调用 va_start() 之后必须调用 va_end() 因为 va_start()(总是?)扰乱堆栈。 任何人都可以解释一下调用 va_start() 是如何修改堆栈的,以及这种修改如何帮助获
对于下面的代码: void fun(char *msg, int n, int m, ...) { va_list ptr; va_start(ptr, m); // Questio
在开始使用 va_list 之前提前退出带有可变参数的函数是否安全? ? #include int func(const char * format, ...){ if(format ==
我要制作varargs一次释放多个指针的函数,主要是为了清理代码。所以我有: void free_all( ... ) { va_list arguments; /* Initiali
为什么下面的代码不起作用? #include #include // People are missing this in their reponses.... 'fmt' here is pas
我正在使用 Visual Studio 2012 编译此示例代码: #include #include const char * __cdecl foo(const char * format,
在对历史悠久的类进行编辑时,我被架构师的一个特殊习惯所困扰,他将 va_start -> va_end 序列包装在互斥锁中。该添加的更改日志(大约 15 年前制作,此后没有修改)指出这是因为 va_s
出于某种原因,我无法正常工作: void examplefunctionname(string str, ...){ ... va_start(ap, str.c_str()); 我也
编辑:我现在有 #include 它编译 - 但可变参数没有通过。有什么想法吗? 我有这段代码(摘录): void msg(char* message, ...) { va_list args
我想创建一些日志记录,我创建了一个类。但是我在将参数传递给它时遇到了一些问题。 类: namespace debug { class log { private:
我必须在嵌入式应用程序中使用 IAR 编译器(它没有命名空间、异常、多重/虚拟继承、模板有点限制并且仅支持 C++03)。我不能使用参数包,所以我尝试使用可变参数创建成员函数。我知道可变参数通常是不安
这是我的最小示例: #include #include #include void print_strings_and_lengths(int count, ...) { va_list
具有以下 header 的函数: int max(int n, va_list vals) 在函数内部调用: int max_first(int n, ...) 需要一个 va_start(vals,
我想使用 va_start 从省略号中检索我的参数。 这是我的代码: char str[256]; void nrf_log_flash(bool is_to_save, char * log, ..
我很难在 x64 中编译我的程序。虽然我能够修复所有问题并进行编译,但我的程序在日志记录时崩溃了: void TLog::VLogAddFormat(COLORREF colorText, const
我试图在我的项目中使用 va_start 和 va_end 函数,但 eclipse 不想将其解析为函数。 gcc 编译整个项目没有错误... [我的文件.cpp] #include #includ
我是一名优秀的程序员,十分优秀!