分享

C、C++可变参数

 心不留意外尘 2016-05-13

http://www.cnblogs.com/aiyelinglong/archive/2012/05/10/2494839.html

2012

由于在C语言中没有函数重载,解决不定数目函数参数的问题变得比较麻烦,即使采用C++,如果参数个数不确定,也很难采用函数重载。

所使用的宏:

Void va_start(va_list arg_ptr, prev_param);

Type va_arg(va_list arg_ptr, type);

Void va_end(va_list, arg_ptr);

 

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 += _INSIZEOF(t)-_INTSIZEOF(t)) //下一个参数地址

#define va_end(ap) (ap = (va_list)0)  //将指针置为无效

 

参数在堆栈中的分布:

在进程中,堆栈地址是从高到低分配,当执行一个函数的时候,将参数列表入栈,压入堆栈的高地址部分,然后入栈函数的返回地址,接着入栈函数的执行代码,这个入栈过程,堆栈地址不断递减,一些黑客就是在堆栈中修改函数返回地址,执行自己的代码来达到执行自己插入的代码段的目的。

参数在堆栈中的分布情况:

最后一个参数
倒数第二个参数
...
第一个参数
函数返回地址
函数代码段

 

 

  1. 首先把va_list被定义成char*,这是因为在我们目前所用的pc机上,字符指针类型可用用来存储内存单元地址
  2. 定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统。这个宏的目的是为了得到最后一个固定参数的实际内存大小。
  3. Va_start的定义为&v+_INTSIZEOF(v),这里&v是最后一个固定参数的起始地址,再加上其实际占用大小后,就得到了第一个可变参数的起始内存地址。所以运行va_start(ap, v)以后,ap指向第一个可变参数在的内存地址。

 

解释_INTSIZEOF(n)  (sizeof(n) + sizeof(int)) & ~(sizeof(int) - 1):

~是位取反的意思。

_INTSIZEOF(n)整个做的事情就是将n的长度化为int长度的整数倍。比如n为5,二进制就是101b,int长度为4,二进制为100b,那么n化为int长度的整数倍就应该为8.~(sizeof(int) - 1)就应该为~(4 -1)=~(00000011b)=11111100b,这样任何数&~(sizeof(int)-1)后最后两位肯定为0,就肯定是4的整数倍了。(sizeof(n) + sizeof(int) - 1)就是将大于4m但是小于等于4(m + 1)的数提高到大于等于4(m +1),但小于4(m + 2),这再&(sizeof(int) - 1)后就正好将原长度补齐到4的倍数。

 

Va_arg():有了va_start的良好基础,我们取得了第一个可变参数的地址,在va_arg()里的任务就是根据指定的参数类型取得本参数的值,并且把指针调到下一个参数的起始地址。

               #define va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))

        这个宏做了两件事,1.用用户输入的类型名对参数地址进行强制类型转换,得到用户所需要的值;2.计算出本参数的实际大小,将指针调到本参数的结尾,也就是下一个参数的首地址,以便后续处理。

        Va_end:x86平台定义为ap=(char*)0,使ap不再指向堆栈,而是跟null一样,有些直接定义为((void*)0),这样不会为va_end产生代码,

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多