http://blog.csdn.net/akirya/archive/2009/03/19/4005258.aspx
首先我们写一个这样的函数,
void Sum(int *e, ...),计算除第一个参数外的和,第一个参数为最终的结果.
很快写下这样的代码
void Sum(int *e, ...) { assert( e ); int n = 0; va_list ap; va_start(ap, e); do{ n = va_arg(ap, int); *e = *e + n ; }while( n ); } int main(int argc,char* argv[]) { int s = 0; Sum( &s , 1,2,3, 0 ); printf( "%d/n" ,s ); return 0; };
经测试一切都没问题.这时候发现第一个参数是指针形式.看起来不太爽.于是改成了引用形式.
改成如下形式
void Sum(int &e, ...) { int n = 0; va_list ap; va_start(ap, e); do{ n = va_arg(ap, int); e = e + n ; }while( n ); } int main(int argc,char* argv[]) { int s = 0; Sum( s , 1,2,3, 0 ); printf( "%d/n" ,s ); return 0; };
少了一个断言,少了几个*号,看起来比较简洁.
运行一下结果大出意料结果是1088609049而且每次都不一样.这是怎么回事?
问题出在使用引用的参数.
我们看一下va_start和va_arg的实现(在这里我只说明Win32系统VC9的情况),将最初正常运行的代码中的宏展开.(VC9下使用/P参数)展开后代码如下
void Sum(int *e, ...) { assert( e ); int n = 0; va_list ap; ( ap = (va_list)( &reinterpret_cast<const char &>(e) ) + ( (sizeof(e) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) ); do{ n = ( *(int *)((ap += ( (sizeof(int) + sizeof(int) - 1) & ~(sizeof(int) - 1) )) - ( (sizeof(int) + sizeof(int) - 1) & ~(sizeof(int) - 1) )) ); *e = *e + n ; }while( n ); }
将代码编译一下,增加临时变量,看看每一步的作用.
void Sum(int *e, ...) { int n = 0; va_list ap; const char* lpAddressE = &reinterpret_cast<const char &>(e) ; ap = (va_list)( lpAddressE ); ap +=( (sizeof(e) + sizeof(int) - 1) & ~(sizeof(int) - 1) ); //ap +=( ( 4 + 4 - 1) & ~(4 -1) ); //ap +=( ( 7 &~3 ) ); //ap +=4; //( ap = (va_list)( &reinterpret_cast<const char &>(e) ) + ( (sizeof(e) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) ); do{ //n = ( *(int *)((ap += ( (4 + 4 - 1) & ~(4 - 1) )) - ( (4 + 4 - 1) & ~(4 - 1) )) ); //n=( *(int *)((ap += ( 7 & ~3 )) - ( 7 & ~3 )) ); //n= *(int *)((ap += 4) - 4) ; ap +=4; n = * (int*)( ap-4 ); //n = ( *(int *)((ap += ( (sizeof(int) + sizeof(int) - 1) & ~(sizeof(int) - 1) )) - ( (sizeof(int) + sizeof(int) - 1) & ~(sizeof(int) - 1) )) ); *e = *e + n ; }while( n ); }
可以看到先取e的地址,然后将地址的值+4,其中va_list是别名定义如下typedef char * va_list;
我们先来分析一下,在进入函数调用的时候栈是下面这种情况
return address//低地址
e
1
2
3
0 //高地址
而va_start的作用就是1的地址赋值给ap,
在va_arg的时候现将ap的地址+4,然后取ap-4位置的内容
也就是说va_arg的时候得到1,然后ap的值+4
依次调用就能变长参数的所有参数值.
va_arg宏中的一系列sizeof计算则保证了移动的时候肯定是4的倍数.
到这里就会清楚在使用 引用做开始参数的时候.也就是va_start的时候会对参数取地址
非引用的时候就会取到在栈上面的参数的地址.要是引用的时候就有问题了,取到的是main函数中s变量的值.
以这个错误的开始地址取内容所得到的结果自然就不对了.