变长参数引起的问题

来源:互联网 发布:网络卫星直播电视 编辑:程序博客网 时间:2024/04/29 21:30

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

 

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

view plaincopy to clipboardprint?
  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. };  


经测试一切都没问题.这时候发现第一个参数是指针形式.看起来不太爽.于是改成了引用形式.
改成如下形式

view plaincopy to clipboardprint?
  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. };  


少了一个断言,少了几个*号,看起来比较简洁.
运行一下结果大出意料结果是1088609049而且每次都不一样.这是怎么回事?
问题出在使用引用的参数.
我们看一下va_start和va_arg的实现(在这里我只说明Win32系统VC9的情况),将最初正常运行的代码中的宏展开.(VC9下使用/P参数)展开后代码如下

view plaincopy to clipboardprint?
  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. }  

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

view plaincopy to clipboardprint?
  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. }  

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

原创粉丝点击