ImageVerifierCode 换一换
格式:DOCX , 页数:18 ,大小:21.78KB ,
资源ID:2749664      下载积分:3 金币
快捷下载
登录下载
邮箱/手机:
温馨提示:
快捷下载时,用户名和密码都是您填写的邮箱或者手机号,方便查询和重复下载(系统自动生成)。 如填写123,账号就是123,密码也是123。
特别说明:
请自助下载,系统不会自动发送文件的哦; 如果您已付费,想二次下载,请登录后访问:我的下载记录
支付方式: 支付宝    微信支付   
验证码:   换一换

加入VIP,免费下载
 

温馨提示:由于个人手机设置不同,如果发现不能下载,请复制以下地址【https://www.bingdoc.com/d-2749664.html】到电脑端继续下载(重复下载不扣费)。

已注册用户请登录:
账号:
密码:
验证码:   换一换
  忘记密码?
三方登录: 微信登录   QQ登录  

下载须知

1: 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。
2: 试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓。
3: 文件的所有权益归上传用户所有。
4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
5. 本站仅提供交流平台,并不能对任何下载内容负责。
6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

版权提示 | 免责声明

本文(valist的问题.docx)为本站会员(b****2)主动上传,冰点文库仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。 若此文所含内容侵犯了您的版权或隐私,请立即通知冰点文库(发送邮件至service@bingdoc.com或直接QQ联系客服),我们立即给予删除!

valist的问题.docx

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

2、个可选参数地址#define va_arg(ap,t) ( *(t *)(ap += _INTSIZEOF(t) - _INTSIZEOF(t) ) /下一个参数地址#define va_end(ap) ( ap = (va_list)0 ) / 将指针置为无效如果对以上几个宏定义不理解,可以略过,接这看后面的内容.3. 参数在堆栈中分布,位置在进程中,堆栈地址是从高到低分配的.当执行一个函数的时候,将参数列表入栈,压入堆栈的高地址部分,然后入栈函数的返回地址,接着入栈函数的执行代码,这个入栈过程,堆栈地址不断递减,一些黑客就是在堆栈中修改函数返回地址,执行自己的代码来达到执行自己插入的代码段

3、的目的.总之,函数在堆栈中的分布情况是:地址从高到低,依次是:函数参数列表,函数返回地址,函数执行代码段.堆栈中,各个函数的分布情况是倒序的.即最后一个参数在列表中地址最高部分,第一个参数在列表地址的最低部分.参数在堆栈中的分布情况如下:最后一个参数倒数第二个参数.第一个参数函数返回地址函数代码段4. 示例代码void arg_test(int i, .);int main(int argc,char *argv)int int_size = _INTSIZEOF(int);printf(int_size=%dn, int_size);arg_test(0, 4);arg_cnt(4,1,2,

4、3,4);return 0;void arg_test(int i, .)int j=0;va_list arg_ptr; va_start(arg_ptr, i); printf(&i = %pn, &i);/打印参数i在堆栈中的地址printf(arg_ptr = %pn, arg_ptr);/打印va_start之后arg_ptr地址,/应该比参数i的地址高sizeof(int)个字节/这时arg_ptr指向下一个参数的地址j=*(int *)arg_ptr);printf(%d %dn, i, j); j=va_arg(arg_ptr, int);printf(arg_ptr = %p

5、n, arg_ptr);/打印va_arg后arg_ptr的地址/应该比调用va_arg前高sizeof(int)个字节/这时arg_ptr指向下一个参数的地址va_end(arg_ptr);printf(%d %dn, i, j);5. 代码说明:int int_size = _INTSIZEOF(int);得到int类型所占字节数va_start(arg_ptr, i); 得到第一个可变参数地址,根据定义(va_list)&v得到起始参数的地址, 再加上_INTSIZEOF(v) ,就是其实参数下一个参数的地址,即第一个可变参数地址.j=va_arg(arg_ptr, int); 得到第一

6、个参参数的值,并且arg_ptr指针上移一个_INTSIZEOF(int),即指向下一个可变参数的地址.va_end(arg_ptr);置空arg_ptr,即arg_ptr=0;总结:读取可变参数的过程其实就是堆栈中,使用指针,遍历堆栈段中的参数列表,从低地址到高地址一个一个地把参数内容读出来的过程.6. 在编程中应该注意的问题和解决办法虽然可以通过在堆栈中遍历参数列表来读出所有的可变参数,但是由于不知道可变参数有多少个,什么时候应该结束遍历,如果在堆栈中遍历太多,那么很可能读取一些无效的数据.解决办法:a.可以在第一个起始参数中指定参数个数,那么就可以在循环还中读取所有的可变参数;b.定义一

7、个结束标记,在调用函数的时候,在最后一个参数中传递这个标记,这样在遍历可变参数的时候,可以根据这个标记结束可变参数的遍历;下面是一段示例代码:/第一个参数定义可选参数个数,用于循环取初参数内容void arg_cnt(int cnt, .);int main(int argc,char *argv)int int_size = _INTSIZEOF(int);printf(int_size=%dn, int_size);arg_cnt(4,1,2,3,4);return 0;void arg_cnt(int cnt, .)int value=0;int i=0;int arg_cnt=cnt;

8、va_list arg_ptr; va_start(arg_ptr, cnt); for(i = 0; i cnt; i+)value = va_arg(arg_ptr,int);printf(value%d=%dn, i+1, value);虽然可以根据上面两个办法解决读取参数个数的问题,但是如果参数类型都是不定的,该怎么办,如果不知道参数的类型,即使读到了参数也没有办法进行处理.解决办法:可以自定义一些可能出现的参数类型,这样在可变参数列表中,可以可变参数列表中的那类型,然后根据类型,读取可变参数值,并进行准确地转换.传递参数的时候可以这样传递:参数数目,可变参数类型1,可变参数值1,可变

9、参数类型2,可变参数值2,.这里给出一个完整的例子:#include #include const int INT_TYPE = 100000;const int STR_TYPE = 100001;const int CHAR_TYPE = 100002;const int LONG_TYPE = 100003;const int FLOAT_TYPE = 100004;const int DOUBLE_TYPE = 100005;/第一个参数定义可选参数个数,用于循环取初参数内容/可变参数采用arg_type,arg_value.的形式传递,以处理不同的可变参数类型void arg_typ

10、e(int cnt, .);/第一个参数定义可选参数个数,用于循环取初参数内容void arg_cnt(int cnt, .);/测试va_start,va_arg的使用方法,函数参数在堆栈中的地址分布情况void arg_test(int i, .);int main(int argc,char *argv)int int_size = _INTSIZEOF(int);printf(int_size=%dn, int_size);arg_test(0, 4);arg_cnt(4,1,2,3,4);arg_type(2, INT_TYPE, 222, STR_TYPE, ok,hello wo

11、rld!);return 0;void arg_test(int i, .)int j=0;va_list arg_ptr; va_start(arg_ptr, i); printf(&i = %pn, &i);/打印参数i在堆栈中的地址printf(arg_ptr = %pn, arg_ptr);/打印va_start之后arg_ptr地址,/应该比参数i的地址高sizeof(int)个字节/这时arg_ptr指向下一个参数的地址j=*(int *)arg_ptr);printf(%d %dn, i, j); j=va_arg(arg_ptr, int);printf(arg_ptr = %

12、pn, arg_ptr);/打印va_arg后arg_ptr的地址/应该比调用va_arg前高sizeof(int)个字节/这时arg_ptr指向下一个参数的地址va_end(arg_ptr);printf(%d %dn, i, j);void arg_cnt(int cnt, .)int value=0;int i=0;int arg_cnt=cnt;va_list arg_ptr; va_start(arg_ptr, cnt); for(i = 0; i cnt; i+)value = va_arg(arg_ptr,int);printf(value%d=%dn, i+1, value);

13、void arg_type(int cnt, .)int arg_type = 0;int int_value=0;int i=0;int arg_cnt=cnt; char *str_value = NULL;va_list arg_ptr; va_start(arg_ptr, cnt);for(i = 0; i cnt; i+)arg_type = va_arg(arg_ptr,int);switch(arg_type)case INT_TYPE:int_value = va_arg(arg_ptr,int);printf(value%d=%dn, i+1, int_value);brea

14、k;case STR_TYPE:str_value = va_arg(arg_ptr,char*);printf(value%d=%dn, i+1, str_value);break;default:break; 有关VA_LIST的用法VA_LIST 是在C语言中解决变参问题的一组宏VA_LIST的用法: (1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针 (2)然后用VA_START宏初始化变量刚定义的VA_LIST变量,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数。 (3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参

15、数的类型。 (4)最后用VA_END宏结束可变参数的获取。然后你就可以在函数里使用第二个参数了。如果函数有多个可变参数的,依次调用VA_ARG获取各个参数。VA_LIST在编译器中的处理:(1)在运行VA_START(ap,v)以后,ap指向第一个可变参数在堆栈的地址。(2)VA_ARG()取得类型t的可变参数值,在这步操作中首先apt = sizeof(t类型),让ap指向下一个参数的地址。然后返回ap-sizeof(t类型)的t类型*指针,这正是第一个可变参数在堆栈里的地址。然后用*取得这个地址的内容。(3)VA_END(),X86平台定义为ap = (char*)0),使ap不再指向堆栈

16、,而是跟NULL一样,有些直接定义为(void*)0),这样编译器不会为VA_END产生代码,例如gcc在Linux的X86平台就是这样定义的。要注意的是:由于参数的地址用于VA_START宏,所以参数不能声明为寄存器变量,或作为函数或数组类型。使用VA_LIST应该注意的问题: (1)因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型. 也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的. (2)另外有一个问题,因为编译器对可变参数的函数的原型检查不

17、够严格,对编程查错不利.不利于我们写出高质量的代码。小结:可变参数的函数原理其实很简单,而VA系列是以宏定义来定义的,实现跟堆栈相关。我们写一个可变函数的C函数时,有利也有弊,所以在不必要的 场合,我们无需用到可变参数,如果在C+里,我们应该利用C+多态性来实现可变参数的功能,尽量避免用C语言的方式来实现。请问:va_list(),va_start()是何意?(一)写一个简单的可变参数的C函数 下面我们来探讨如何写一个简单的可变参数的C函数.写可变参数的 C函数要在程序中用到以下这些宏: void va_start( va_list arg_ptr, prev_param ); type va

18、_arg( va_list arg_ptr, type ); void va_end( va_list arg_ptr ); va在这里是variable-argument(可变参数)的意思. 这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个 头文件.下面我们写一个简单的可变参数的函数,改函数至少有一个整数 参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值. void simple_va_fun(int i, .) va_list arg_ptr; int j=0; va_start(arg_ptr, i); j=va_arg(arg_ptr, int); va

19、_end(arg_ptr); printf(%d %dn, 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,这个宏的第二个参数是第 一个可变参数的前一个参数,是一个固定

20、的参数. 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 我

21、们看到第一种调用有错误,第二种调用正确,第三种调用尽管结果 正确,但和我们函数最初的设计有冲突.下面一节我们探讨出现这些结果 的原因和可变参数在编译器中是如何处理的. (二)可变参数在编译器中的处理 我们知道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

22、) - 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的

23、定义,定义为&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)

24、 ); 首先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),

25、这样编译器不 会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所 以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的 是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的. (三)可变参数在编程中要注意的问题 因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢, 可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能 地识别不同参数的个数和类型. 有人会问:那么printf中不

26、是实现了智能识别参数吗?那是因为函数 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); prin

27、tf(%d %sn, 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头文件中所定义的

28、宏,ANSI C的宏跟system V的宏是不兼容的,我们一般都用ANSI C,所以 原文载于:VA_LIST 是在C语言中解决变参问题的一组宏他有这么几个成员:1) va_list型变量:#ifdef _M_ALPHAtypedef struct char *a0; /* pointer to first homed integer argument */ int offset; /* byte offset of next parameter */ va_list;#elsetypedef char * va_list;#endif2)_INTSIZEOF 宏,获取类型占用的空间长度,最小

29、占用长度为int的整数倍:#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & (sizeof(int) - 1) )3)VA_START宏,获取可变参数列表的第一个参数的地址(ap是类型为va_list的指针,v是可变参数最左边的参数):#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )4)VA_ARG宏,获取可变参数的当前参数,返回指定类型并将指针指向下一参数(t参数描述了当前参数的类型):#define va_arg(ap,t) ( *(t *)(ap += _INTSIZEOF(t) - _INTSIZEOF(t) )5)VA_END宏,清空va_list可变参数列表:#define va_end(ap) (

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2