您的位置:首页 > 编程语言 > C语言/C++

深度剖析C语言可变参数列表

2015-11-29 18:42 232 查看


C语言可变参数函数分析与实现


1.可变参数的函数声明:

void PrintFloats ( int amount, ...);
[/code]
注:声明中函数的第一个参数名必须是可知的(如例amout参数名开始,而非直接...开始)因为要通过宏确定其传入参数起始地址及个数等问题。


2.使用可变参数函数的具体实现过程:

/* va_start example */
#include <stdio.h>
#include <stdarg.h>

void PrintFloats ( int amount, ...)
{
int i;
double val;
printf ("Floats passed: ");
va_list vl; 		//定义va_list 变量用于确定参数
va_start(vl,amount);	//确定函数传入参数的第一个地址
for (i=0;i<amount;i++)
{
val=va_arg(vl,double);//相继的去处下一个参数,然后进行操作
printf ("\t%.2f",val);
}
va_end(vl);		//结束标志,这个宏只是看起来和va_start 对称而已
printf ("\n");
}

int main ()
{
PrintFloats (3,3.14159,2.71828,1.41421);
return 0;
}
[/code]

3.可变参数的内部实现

相关类型的原型(在代码中右击跳转到定义):
typedef char * va_list;

#define va_start _crt_va_start

#define va_arg _crt_va_arg

#define va_end _crt_va_end

#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 )

#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

具体分析:
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )

这个宏通过类型转换取得参数v的地址。也就是如上例子中的&amount

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

由参数的传递过程来看,这里的n就是上边的v也就是&amount
那么这个式子就是求参数的某个大小了。:
#define _INTSIZEOF(n) 对于上式amount 为int类型有:

( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

=((4 + 4 - 1) & ~(4 - 1) )

=((7 ) & ~(3 )

= 0111 & ~(0011)

= 0111 & ~(1100)

= 0100 = 4(这不正是int的字节对齐数么)

#define _INTSIZEOF(n) 假设传入参数为char类型:

( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

=((1 + 4 - 1) & ~(4 - 1) )

=((4 ) & ~(3 )

= 0100 & ~(0011)

= 0100 & ~(1100)

= 0100 = 4(这不正是x86下的默认字节齐数么)

可看出这个只是什么东东了? 它就是传说中的字节对齐数处理。由于传入参数压栈是由此规矩搞定,那么现在使用我们也就需要用到他了。

#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
有以上的分析,对此式子有:ap = (char*)(&amount)+(amount参数的字节对齐数)
//也就是下一个变量的地址了。

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



所以,实际上,该表达式是反悔了当前地址的变量,而将ap指向下一个地址。

#define _crt_va_end(ap) ( ap = (va_list)0 )

这个就简单了,设置ap = (char*)0; 也就是设置为空。其实这句是可有可无的,只是和start对称,程序的完整性。。或者你也可以理解为他是安全着想(然而影响并没有什么)

至此,我们已经将所有相关的宏进行解读剖析完成。可变参数也就成为了我们使用的一大利器。

4.扩展

另外补充一点的是,这个参数中,最重要的就是对字节对齐的处理了。。这个问题是平台相关的问题了。
#elif defined(_M_IX86)

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



这里对应的系统情况下,默认对齐取得是int.所以这也就解释了另外一个问题。(如下例)

以下是一个简单的程序片段

int ADD(char a,...)

{

int val;

cout << &val << endl;

int main()

{

int a = 'a';

cout << &a << endl;

char res = ADD('a','b','b','b','a',-1);

这个问题就是说,对于用到可变参数的地方,参数传递是按照每个参数分别计算字节对齐((1补齐为4)+4=8),而后压栈的,而非一起处理字节对齐而压栈(此情形时应当差距4(1+1--》补齐为4)字节).
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
注:这个真心有点坑。原来不能直接对于图片进行复制粘贴进来,搞得我重新补充/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: