变长参数引起的问题

 http://blog.csdn.net/akirya/archive/2009/03/19/4005258.aspx

 

首先我们写一个这样的函数,
void Sum(int *e, ...),计算除第一个参数外的和,第一个参数为最终的结果.
很快写下这样的代码

view plain copy to clipboard print ?
  1. void  Sum( int  *e, ...)  
  2. {  
  3.  assert( e );  
  4.  int  n = 0;  
  5.  va_list  ap;  
  6.  va_start(ap, e);  
  7.  do {  
  8.   n = va_arg(ap, int );  
  9.   *e = *e + n ;  
  10.  }while ( n );  
  11. }  
  12. int  main( int  argc, char * argv[])  
  13. {  
  14.  int  s = 0;  
  15.  Sum( &s , 1,2,3, 0 );  
  16.  printf( "%d/n"  ,s );  
  17.  return  0;  
  18. };  

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; };
经测试一切都没问题.这时候发现第一个参数是指针形式.看起来不太爽.于是改成了引用形式.
改成如下形式

view plain copy to clipboard print ?
  1. void  Sum( int  &e, ...)  
  2. {  
  3.  int  n = 0;  
  4.  va_list  ap;  
  5.  va_start(ap, e);  
  6.  do {  
  7.   n = va_arg(ap, int );  
  8.   e = e + n ;  
  9.  }while ( n );  
  10. }  
  11. int  main( int  argc, char * argv[])  
  12. {  
  13.  int  s = 0;  
  14.  Sum( s , 1,2,3, 0 );  
  15.  printf( "%d/n"  ,s );  
  16.  return  0;  
  17. };  

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参数)展开后代码如下

view plain copy to clipboard print ?
  1. void  Sum( int  *e, ...)  
  2. {  
  3.  assert( e );  
  4.  int  n = 0;  
  5.  va_list  ap;  
  6.  ( ap = (va_list )( & reinterpret_cast < const   char  &>(e) ) + ( ( sizeof (e) +  sizeof ( int ) - 1) & ~( sizeof ( int ) - 1) ) );  
  7.  do {  
  8.   n = ( *(int  *)((ap += ( ( sizeof ( int ) +  sizeof ( int ) - 1) & ~( sizeof ( int ) - 1) )) - ( ( sizeof ( int ) +  sizeof ( int ) - 1) & ~( sizeof ( int ) - 1) )) );  
  9.   *e = *e + n ;  
  10.  }while ( n );  
  11. }  

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 ); }

将代码编译一下,增加临时变量,看看每一步的作用.

view plain copy to clipboard print ?
  1. void  Sum( int  *e, ...)  
  2. {  
  3.  int  n = 0;  
  4.  va_list  ap;  
  5.  const   char * lpAddressE = & reinterpret_cast < const   char  &>(e) ;  
  6.  ap = (va_list )( lpAddressE );  
  7.   
  8.  ap   +=( (sizeof (e) +  sizeof ( int ) - 1) & ~( sizeof ( int ) - 1) );  
  9.  //ap +=( (       4   +     4      - 1) & ~(4            -1) );   
  10.  //ap +=( ( 7 &~3 ) );   
  11.  //ap +=4;   
  12.  //( ap = (va_list)( &reinterpret_cast<const char &>(e) ) + ( (sizeof(e) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) );   
  13.  do {  
  14.   //n = ( *(int *)((ap += ( (4 + 4 - 1) & ~(4 - 1) )) - ( (4 + 4 - 1) & ~(4 - 1) )) );   
  15.   //n=( *(int *)((ap += ( 7 & ~3 )) - ( 7 & ~3 )) );   
  16.     
  17.   //n= *(int *)((ap += 4) - 4) ;   
  18.   ap +=4;  
  19.   n = *  (int *)( ap-4 );  
  20.   //n = ( *(int *)((ap += ( (sizeof(int) + sizeof(int) - 1) & ~(sizeof(int) - 1) )) - ( (sizeof(int) + sizeof(int) - 1) & ~(sizeof(int) - 1) )) );   
  21.   *e = *e + n ;  
  22.  }while ( n );  
  23. }  

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变量的值.
以这个错误的开始地址取内容所得到的结果自然就不对了.

你可能感兴趣的:(list,测试)