c语言中可变参数函数的设计

1,首先,怎么得到参数的值。对于一般的函数,我们可以通过参数对应在参数列表里的标识符来得到。但是参数可变函数那些可变的参数是没有参数标识符的,它只有“…”,所以通过标识符来得到是不可能的,我们只有另辟途径。

我们知道函数调用时都会分配栈空间,而函数调用机制中的栈结构如下图所示:

                       |     ......     |

                       ------------------

                       |     参数2      |

                       ------------------

                       |     参数1      |

                       ------------------

                       |    返回地址    |

                       ------------------

                       |调用函数运行状态|

                       ------------------

可见,参数是连续存储在栈里面的,那么也就是说,我们只要得到可变参数的前一个参数的地址,就可以通过指针访问到那些可变参数。但是怎么样得到可变参数的前一个参数的地址呢?不知道你注意到没有,参数可变函数在可变参数之前必有一个参数是固定的,并使用标识符,而且通常被声明为char*类型,printf函数也不例外。这样的话,我们就可以通过这个参数对应的标识符来得到地址,从而访问其他参数变得可能。我们可以写一个测试程序来试一下:

#include <stdio.h>



void va_test(char* fmt,...);//参数可变的函数声明



void main()

{

    int a=1,c=55;

       char b='b';

    va_test("",a,b,c);//用四个参数做测试

}



void va_test(char* fmt,...) //参数可变的函数定义,注意第一个参数为char* fmt

{

   char *p=NULL;



      p=(char *)&fmt;//注意不是指向fmt,而是指向&fmt,并且强制转化为char *,以便一个一个字节访问

      for(int i = 0;i<16;i++)//16是通过计算的值(参数个数*4个字节),只是为了测试,暂且将就一下

      {

                printf("%.4d ",*p);//输出p指针指向地址的值

        p++;

      }

}



编译运行的结果为

0056 0000 0066 0000 | 0001 0000 0000 0000 | 0098 0000 0000 0000 | 0055 0000 0000 0000



由运行结果可见,通过这样方式可以逐一获得可变参数的值。

至于为什么通常被声明为char*类型,我们慢慢看来。

2,怎样确定参数类型和数量

通过上述的方式,我们首先解决了取得可变参数值的问题,但是对于一个参数,值很重要,其类型同样举足轻重,而对于一个函数来讲参数个数也非常重要,否则就会产生了一系列的麻烦来。通过访问存储参数的栈空间,我们并不能得到关于类型的任何信息和参数个数的任何信息。我想你应该想到了——使用char *参数。Printf函数就是这样实现的,它把后面的可变参数类型都放到了char *指向的字符数组里,并通过%来标识以便与其它的字符相区别,从而确定了参数类型也确定了参数个数。其实,用何种方式来到达这样的效果取决于函数的实现。比如说,定义一个函数,预知它的可变参数类型都是int,那么固定参数完全可以用int类型来替换char*类型,因为只要得到参数个数就可以了。

3,言归正传

   我想到了这里,大概的轮廓已经呈现出来了。本来想就此作罢的(我的惰性使然),但是一想到如果不具实用性便可能是一堆废物,枉费我打了这么些字,决定还是继续下去。

   我是比较抵制用那些不明所以的宏定义的,所以在上面的阐述里一点都没有涉及定义在<stdarg.h>的va(variable-argument)宏。事实上,当时让我产生极大疑惑和好奇的正是这几个宏定义。但是现在我们不得不要去和这些宏定义打打交道,毕竟我们在讨生计的时候还得用上他们,这也是我曰之为“言归正传”的理由。

   好了,我们来看一下那些宏定义。

   打开<stdarg.h>文件,找一下va_*的宏定义,发现不单单只有一组,但是在各组定义前都会有宏编译。宏编译指示的是不同硬件平台和编译器下用怎样的va宏定义。比较一下,不同之处主要在偏移量的计算上。我们还是拿个典型又熟悉的——X86的相关宏定义:

1)typedef char * va_list;

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



3)#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )

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

5)#define va_end(ap)      ( ap = (va_list)0 )



我们逐一看来:

第一个我想不必说了,类型定义罢了。第二个是颇有些来头的,我们也不得不搞懂它,因为后面的两个关键的宏定义都用到了。不知道你够不够细心,有没有发现在上面的测试程序中,第二个可变参数明明是char类型,可是在输出结果中占了4个byte。难道所有的参数都会占4个byte的空间?那如果是double类型的参数,且不是会丢失数据!如果你不嫌麻烦的话,再去做个测试吧,在上面的测试程序中用一个double类型(长度为8byte)和一个long double类型(长度为10byte)做可变参数。发现什么?double类型占了8byte,而long double占了12byte。好像都是4的整数倍哦。不得不引出另一个概念了“对齐(alignment)”,所谓对齐,对Intel80x86 机器来说就是要求每个变量的地址都是sizeof(int)的倍数。原来我们搞错了,char类型的参数只占了1byte,但是它后面的参数因为对齐的关系只能跳过3byte存储,而那3byte也就浪费掉了。那为什么要对齐?因为在对齐方式下,CPU 的运行效率要快得多(举个例子吧,要说明的是下面的例子是我从网上摘录下来的,不记得出处了。

示例:如下图,当一个long 型数(如图中long1)在内存中的位置正好与内存的字边界对齐时,CPU 存取这个数只需访问一次内存,而当一个long 型数(如图中的long2)在内存中的位置跨越了字边界时,CPU 存取这个数就需要多次访问内存,如i960cx 访问这样的数需读内存三次(一个BYTE、一个SHORT、一个BYTE,由CPU 的微代码执行,对软件透明),所以对齐方式下CPU 的运行效率明显快多了。

1       8       16      24      32  

------- ------- ------- ---------

| long1 | long1 | long1 | long1 |

------- ------- ------- ---------

|        |        |         | long2 |

------- ------- ------- ---------

| long2 | long2 | long2 |        |

------- ------- ------- ---------

| ....)。好像扯得有点远来,但是有助于对_INTSIZEOF(n)的理解。位操作对于我来说是玄的东东。单个位运算还应付得来,而这样一个表达式摆在面前就晕了。怎么办?菜鸟自有菜的办法。(待续)


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=14721

---------------------------------------------------------------------------------------------------------------------

C语言中的可变参数函数    CSDN Blog推出文章指数概念,文章指数是对Blog文章综合评分后推算出的,综合评分项分别是该文章的点击量,回复次数,被网摘收录数量,文章长度和文章类型;满分100,每月更新一次。

第一篇

C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:

int printf( const char* format, ...);

它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点“…”做参数占位符),实际调用时可以有以下的形式:

printf("%d",i);
printf("%s",s);
printf("the number is %d ,string is:%s", i, s);  

一个简单的可变参数的C函数

     先看例子程序。该函数至少有一个整数参数,其后占位符…,表示后面参数的个数不定。在这个例子里,所有的输入参数必须都是整数,函数的功能只是打印所有参数的值。函数代码如下:

//示例代码1:可变参数函数的使用
#include "stdio.h"
#include "stdarg.h"
void simple_va_fun(int start, ...)
{
    va_list arg_ptr;
    int nArgValue =start;
    int nArgCout="0"; //可变参数的数目
    va_start(arg_ptr,start); //以固定参数的地址为起点确定变参的内存起始地址。
    do
    {
        ++nArgCout;
        printf("the %d th arg: %d",nArgCout,nArgValue); //输出各参数的值
        nArgValue = va_arg(arg_ptr,int); //得到下一个可变参数的值
    } while(nArgValue != -1);              
    return;
}
int main(int argc, char* argv[])
{
    simple_va_fun(100,-1);
    simple_va_fun(100,200,-1);
    return 0;
}

下面解释一下这些代码。从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:

⑴由于在程序中将用到以下这些宏:
void va_start( va_list arg_ptr, prev_param );
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
va在这里是variable-argument(可变参数)的意思。
这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个头文件。

⑵函数里首先定义一个va_list型的变量,这里是arg_ptr,这个变量是存储参数地址的指针.因为得到参数的地址之后,再结合参数的类型,才能得到参数的值。

⑶然后用va_start宏初始化⑵中定义的变量arg_ptr,这个宏的第二个参数是可变参数列表的前一个参数,即最后一个固定参数。

⑷然后依次用va_arg宏使arg_ptr返回可变参数的地址,得到这个地址之后,结合参数的类型,就可以得到参数的值。

⑸设定结束条件,这里的条件就是判断参数值是否为-1。注意被调的函数在调用时是不知道可变参数的正确数目的,程序员必须自己在代码中指明结束条件。至于为什么它不会知道参数的数目,在看完这几个宏的内部实现机制后,自然就会明白。

第二篇

C语言之可变参数问题



C语言中有一种长度不确定的参数,形如:"…",它主要用在参数个数不确定的函数中,我们最容易想到的例子是printf函数。

  原型:

  int printf( const char *format [, argument]... );

  使用例:

  printf("Enjoy yourself everyday!\n");

  printf("The value is %d!\n", value);

  这种可变参数可以说是C语言一个比较难理解的部分,这里会由几个问题引发一些对它的分析。

  注意:在C++中有函数重载(overload)可以用来区别不同函数参数的调用,但它还是不能表示任意数量的函数参数。

  问题:printf的实现

  请问,如何自己实现printf函数,如何处理其中的可变参数问题? 答案与分析:

  在标准C语言中定义了一个头文件专门用来对付可变参数列表,它包含了一组宏,和一个va_list的typedef声明。一个典型实现如下:

  typedef char* va_list;

  #define va_start(list) list = (char*)&va_alist

  #define va_end(list)

  #define va_arg(list, mode)\

  ((mode*) (list += sizeof(mode)))[-1]

  自己实现printf:

  #include

  int printf(char* format, …)

  {

  va_list ap;

  va_start(ap, format);

  int n = vprintf(format, ap);

  va_end(ap);

  return n;

  }

  问题:运行时才确定的参数

  有没有办法写一个函数,这个函数参数的具体形式可以在运行时才确定?

  答案与分析:

  目前没有"正规"的解决办法,不过独门偏方倒是有一个,因为有一个函数已经给我们做出了这方面的榜样,那就是main(),它的原型是:

  int main(int argc,char *argv[]);
函数的参数是argc和argv。

  深入想一下,"只能在运行时确定参数形式",也就是说你没办法从声明中看到所接受的参数,也即是参数根本就没有固定的形式。常用的办法是你可以通过定义一个void *类型的参数,用它来指向实际的参数区,然后在函数中根据根据需要任意解释它们的含义。这就是main函数中argv的含义,而argc,则用来表明实际的参数个数,这为我们使用提供了进一步的方便,当然,这个参数不是必需的。

  虽然参数没有固定形式,但我们必然要在函数中解析参数的意义,因此,理所当然会有一个要求,就是调用者和被调者之间要对参数区内容的格式,大小,有效性等所有方面达成一致,否则南辕北辙各说各话就惨了。

  问题:可变长参数的传递

  有时候,需要编写一个函数,将它的可变长参数直接传递给另外的函数,请问,这个要求能否实现?

  答案与分析:

  目前,你尚无办法直接做到这一点,但是我们可以迂回前进,首先,我们定义被调用函数的参数为va_list类型,同时在调用函数中将可变长参数列表转换为va_list,这样就可以进行变长参数的传递了。看如下所示:

  void subfunc (char *fmt, va_list argp)

  {

  ...

  arg = va_arg (fmt, argp); /* 从argp中逐一取出所要的参数 */

  ...

  }

  void mainfunc (char *fmt, ...)

  {

  va_list argp;

  va_start (argp, fmt); /* 将可变长参数转换为va_list */

  subfunc (fmt, argp); /* 将va_list传递给子函数 */

  va_end (argp);

  ...

  }

  问题:可变长参数中类型为函数指针

  我想使用va_arg来提取出可变长参数中类型为函数指针的参数,结果却总是不正确,为什么?

  答案与分析:

  这个与va_arg的实现有关。一个简单的、演示版的va_arg实现如下:

  #define va_arg(argp, type) \

  (*(type *)(((argp) += sizeof(type)) - sizeof(type)))

  其中,argp的类型是char *。

  如果你想用va_arg从可变参数列表中提取出函数指针类型的参数,例如

  int (*)(),则va_arg(argp, int (*)())被扩展为:

  (*(int (*)() *)(((argp) += sizeof (int (*)())) -sizeof (int (*)())))

  显然,(int (*)() *)是无意义的。

  解决这个问题的办法是将函数指针用typedef定义成一个独立的数据类型,例如:

  typedef int (*funcptr)();

  这时候再调用va_arg(argp, funcptr)将被扩展为:

  (* (funcptr *)(((argp) += sizeof (funcptr)) - sizeof (funcptr)))

  这样就可以通过编译检查了。

  问题:可变长参数的获取

  有这样一个具有可变长参数的函数,其中有下列代码用来获取类型为float的实参:

  va_arg (argp, float);

  这样做可以吗?

  答案与分析:

  不可以。在可变长参数中,应用的是"加宽"原则。也就是float类型被扩展成double;char, short被扩展成int。因此,如果你要去可变长参数列表中原来为float类型的参数,需要用va_arg(argp, double)。对char和short类型的则用va_arg(argp, int)。

  问题:定义可变长参数的一个限制

  为什么我的编译器不允许我定义如下的函数,也就是可变长参数,但是没有任何的固定参数?

  int f (...)

  {

  ...

  }

  答案与分析:

  不可以。这是ANSI C 所要求的,你至少得定义一个固定参数。

  这个参数将被传递给va_start(),然后用va_arg()和va_end()来确定所有实际调用时可变长参数的类型和值。---------------------------------------------------------------------------------------------------------------------
如何判别可变参数函数的参数类型?

函数形式如下:
void   fun(char*   str,...)
{
      ......
}

若传的参数个数大于1,如何判别第2个以后传参的参数类型???
最好有源码说明!


没办法判断的


如楼上所说,例如printf( "%d%c%s ",   ....)是通过格式串中的%d,   %c,   %s来确定后面参数的类型,其实你也可以参考这种方法来判断不定参数的类型.


无法判断。可变参数实现主要通过三个宏实现:va_start,   va_arg,   va_end。


六、 扩展与思考

个数可变参数在声明时只需"..."即可;但是,我们在接受这些参数时不能"..."。va函数实现的关键就是如何得到参数列表中可选参数,包括参数的值和类型。以上的所有实现都是基于来自stdarg.h的va_xxx的宏定义。 <思考>能不能不借助于va_xxx,自己实现VA呢?,我想到的方法是汇编。在C中,我们当然就用C的嵌入汇编来实现,这应该是可以做得到的。至于能做到什么程度,稳定性和效率怎么样,主要要看你对内存和指针的控制了。

参考资料

1.IEEE和OpenGroup联合开发的Single Unix specification Ver3;BR>
2.Linux man手册;

3.x86汇编,还有一些安全编码方面的资料。

---------------------------------------------------------------------------------------------------------------------
[转帖]对C/C++可变参数表的深层探索

C/C++语言有一个不同于其它语言的特性,即其支持可变参数,典型的函数如printf、scanf等可以接受数量不定的参数。如:
  printf ( "I love you" );
  printf ( "%d", a );
  printf ( "%d,%d", a, b );
  第一、二、三个printf分别接受1、2、3个参数,让我们看看printf函数的原型:
  int printf ( const char *format, ... );
  从函数原型可以看出,其除了接收一个固定的参数format以外,后面的参数用"…"表示。在C/C++语言中,"…"表示可以接受不定数量的参数,理论上来讲,可以是0或0以上的n个参数。
  本文将对C/C++可变参数表的使用方法及C/C++支持可变参数表的深层机理进行探索。

  一. 可变参数表的用法
  1、相关宏
  标准C/C++包含头文件stdarg.h,该头文件中定义了如下三个宏:
void va_start ( va_list arg_ptr, prev_param ); /* ANSI version */
type va_arg ( va_list arg_ptr, type );
void va_end ( va_list arg_ptr );
  在这些宏中,va就是variable argument(可变参数)的意思;arg_ptr是指向可变参数表的指针;prev_param则指可变参数表的前一个固定参数;type为可变参数的类型。va_list也是一个宏,其定义为typedef char * va_list,实质上是一 char型指针。char型指针的特点是++、--操作对其作用的结果是增1和减1(因为sizeof(char)为1),与之不同的是int等其它类型指针的++、--操作对其作用的结果是增sizeof(type)或减sizeof(type),而且sizeof (type)大于1。
  通过va_start宏我们可以取得可变参数表的首指针,这个宏的定义为:
#define va_start ( ap, v ) ( ap = (va_list)&v + _INTSIZEOF(v) )
  显而易见,其含义为将最后那个固定参数的地址加上可变参数对其的偏移后赋值给ap,这样ap就是可变参数表的首地址。其中的_INTSIZEOF宏定义为:
#define _INTSIZEOF(n) ((sizeof ( n ) + sizeof ( int ) - 1 ) & ~( sizeof( int ) - 1 ) )
  va_arg宏的意思则指取出当前arg_ptr所指的可变参数并将ap指针指向下一可变参数,其原型为:
#define va_arg(list, mode) ((mode *)(list =(char *) ((((int)list + (__builtin_alignof(mode)<=4?3:7)) &(__builtin_alignof(mode)<=4?-4:-8))+sizeof(mode))))[-1]
  对这个宏的具体含义我们将在后面深入讨论。
  而va_end宏被用来结束可变参数的获取,其定义为:
#define va_end ( list )
  可以看出,va_end ( list )实际上被定义为空,没有任何真实对应的代码,用于代码对称,与va_start对应;另外,它还可能发挥代码的"自注释"作用。所谓代码的"自注释",指的是代码能自己注释自己。
  下面我们以具体的例子来说明以上三个宏的使用方法。
  2、一个简单的例子
  #include <stdarg.h>
  /* 函数名:max
  * 功能:返回n个整数中的最大值
  * 参数:num:整数的个数 ...:num个输入的整数
  * 返回值:求得的最大整数
  */
  int max ( int num, ... )
  {
   int m = -0x7FFFFFFF; /* 32系统中最小的整数 */
   va_list ap;
   va_start ( ap, num );
   for ( int i= 0; i< num; i++ )
   {
    int t = va_arg (ap, int);
    if ( t > m )
    {
     m = t;
    }
   }
   va_end (ap);
   return m;
  }
  /* 主函数调用max */
  int main ( int argc, char* argv[] )
  {
   int n = max ( 5, 5, 6 ,3 ,8 ,5); /* 求5个整数中的最大值 */
   cout << n;
   return 0;
  }
  函数max中首先定义了可变参数表指针ap,而后通过va_start ( ap, num )取得了参数表首地址(赋给了ap),其后的for循环则用来遍历可变参数表。这种遍历方式与我们在数据结构教材中经常看到的遍历方式是类似的。
  函数max看起来简洁明了,但是实际上printf的实现却远比这复杂。max函数之所以看起来简单,是因为:
  (1) max函数可变参数表的长度是已知的,通过num参数传入;
  (2) max函数可变参数表中参数的类型是已知的,都为int型。
  而printf函数则没有这么幸运。首先,printf函数可变参数的个数不能轻易的得到,而可变参数的类型也不是固定的,需由格式字符串进行识别(由%f、%d、%s等确定),因此则涉及到可变参数表的更复杂应用。
  下面我们以实例来分析可变参数表的高级应用。

  二. 高级应用
  下面这个程序是我们为某嵌入式系统(该系统中CPU的字长为16位)编写的在屏幕上显示格式字符串的函数DrawText,它的用法类似于 int printf ( const char *format, ... )函数,但其输出的目标为嵌入式系统的液晶显示屏幕(LED)。
  ///////////////////////////////////////////////////////////////////////////////
  // 函数名称: DrawText
  // 功能说明: 在显示屏上绘制文字
  // 参数说明: xPos ---横坐标的位置 [0 .. 30]
  // yPos ---纵坐标的位置 [0 .. 64]
  // ... 可以同数字一起显示,需设置标志(%d、%l、%x、%s)
  ///////////////////////////////////////////////////////////////////////////////
  extern void DrawText ( BYTE xPos, BYTE yPos, LPBYTE lpStr, ... )
  {
   BYTE lpData[100]; //缓冲区
   BYTE byIndex;
   BYTE byLen;
   DWORD dwTemp;
   WORD wTemp;
   int i;
   va_list lpParam;
   memset( lpData, 0, 100);
   byLen = strlen( lpStr );
   byIndex = 0;
   va_start ( lpParam, lpStr );
   for ( i = 0; i < byLen; i++ )
   {
    if( lpStr[i] != ’%’ ) //不是格式符开始
    {
     lpData[byIndex++] = lpStr[i];
    }
    else
    {
     switch (lpStr[i+1])
     {
      //整型
      case ’d’:
      case ’D’:
       wTemp = va_arg ( lpParam, int );
       byIndex += IntToStr( lpData+byIndex, (DWORD)wTemp );
       i++;
       break;
      //长整型
      case ’l’:
      case ’L’:
       dwTemp = va_arg ( lpParam, long );
       byIndex += IntToStr ( lpData+byIndex, (DWORD)dwTemp );
       i++;
       break;
      //16进制(长整型)
      case ’x’:
      case ’X’:
       dwTemp = va_arg ( lpParam, long );
       byIndex += HexToStr ( lpData+byIndex, (DWORD)dwTemp );
       i++;
       break;
      default:
       lpData[byIndex++] = lpStr[i];
       break;
     }
    }
   }
   va_end ( lpParam );
   lpData[byIndex] = ’#CONTENT#’;
   DisplayString ( xPos, yPos, lpData, TRUE); //在屏幕上显示字符串lpData
  }
  在这个函数中,需通过对传入的格式字符串(首地址为lpStr)进行识别来获知可变参数个数及各个可变参数的类型,具体实现体现在for循环中。譬如,在识别为%d后,做的是va_arg ( lpParam, int ),而获知为%l和%x后则进行的是va_arg ( lpParam, long )。格式字符串识别完成后,可变参数也就处理完了。
  在项目的最初,我们一直苦于不能找到一个好的办法来混合输出字符串和数字,我们采用了分别显示数字和字符串的方法,并分别指定坐标,程序条理被破坏。而且,在混合显示的时候,要给各类数据分别人工计算坐标,我们感觉头疼不已。以前的函数为:
  //显示字符串
  showString ( BYTE xPos, BYTE yPos, LPBYTE lpStr )
  //显示数字
  showNum ( BYTE xPos, BYTE yPos, int num )
  //以16进制方式显示数字
  showHexNum ( BYTE xPos, BYTE yPos, int num )
  最终,我们用DrawText ( BYTE xPos, BYTE yPos, LPBYTE lpStr, ... )函数代替了原先所有的输出函数,程序得到了简化。就这样,兄弟们用得爽翻了。

  三. 运行机制探索
  通过第2节我们学会了可变参数表的使用方法,相信喜欢抛根问底的读者还不甘心,必然想知道如下问题:
  (1)为什么按照第2节的做法就可以获得可变参数并对其进行操作?
  (2)C/C++在底层究竟是依靠什么来对这一语法进行支持的,为什么其它语言就不能提供可变参数表呢?
  我们带着这些疑问来一步步进行摸索。
  3.1 调用机制反汇编
  反汇编是研究语法深层特性的终极良策,先来看看2.2节例子中主函数进行max ( 5, 5, 6 ,3 ,8 ,5)调用时的反汇编:
  1. 004010C8 push 5
  2. 004010CA push 8
  3. 004010CC push 3
  4. 004010CE push 6
  5. 004010D0 push 5
  6. 004010D2 push 5
  7. 004010D4 call @ILT+5(max) (0040100a)
  从上述反汇编代码中我们可以看出,C/C++函数调用的过程中:
  第一步:将参数从右向左入栈(第1~6行);
  第二步:调用call指令进行跳转(第7行)。
  这两步包含了深刻的含义,它说明C/C++默认的调用方式为由调用者管理参数入栈的操作,且入栈的顺序为从右至左,这种调用方式称为_cdecl调用。x86系统的入栈方向为从高地址到低地址,故第1至n个参数被放在了地址递增的堆栈内。在被调用函数内部,读取这些堆栈的内容就可获得各个参数的值,让我们反汇编到max函数的内部:
  int max ( int num, ...)
  {
  1. 00401020 push ebp
  2. 00401021 mov ebp,esp
  3. 00401023 sub esp,50h
  4. 00401026 push ebx
  5. 00401027 push esi
  6. 00401028 push edi
  7. 00401029 lea edi,[ebp-50h]
  8. 0040102C mov ecx,14h
  9. 00401031 mov eax,0CCCCCCCCh
  10. 00401036 rep stos dword ptr [edi]
  va_list ap;
  int m = -0x7FFFFFFF; /* 32系统中最小的整数 */
  11. 00401038 mov dword ptr [ebp-8],80000001h
  va_start ( ap, num );
  12. 0040103F lea eax,[ebp+0Ch]
  13. 00401042 mov dword ptr [ebp-4],eax
  for ( int i= 0; i< num; i++ )
  14. 00401045 mov dword ptr [ebp-0Ch],0
  15. 0040104C jmp max+37h (00401057)
  16. 0040104E mov ecx,dword ptr [ebp-0Ch]
  17. 00401051 add ecx,1
  18. 00401054 mov dword ptr [ebp-0Ch],ecx
  19. 00401057 mov edx,dword ptr [ebp-0Ch]
  20. 0040105A cmp edx,dword ptr [ebp+8]
  21. 0040105D jge max+61h (00401081)
  {
   int t= va_arg (ap, int);
   22. 0040105F mov eax,dword ptr [ebp-4]
   23. 00401062 add eax,4
   24. 00401065 mov dword ptr [ebp-4],eax
   25. 00401068 mov ecx,dword ptr [ebp-4]
   26. 0040106B mov edx,dword ptr [ecx-4]
   27. 0040106E mov dword ptr [t],edx
   if ( t > m )
    28. 00401071 mov eax,dword ptr [t]
    29. 00401074 cmp eax,dword ptr [ebp-8]
    30. 00401077 jle max+5Fh (0040107f)
    m = t;
    31. 00401079 mov ecx,dword ptr [t]
    32. 0040107C mov dword ptr [ebp-8],ecx
   }
   33. 0040107F jmp max+2Eh (0040104e)
   va_end (ap);
   34. 00401081 mov dword ptr [ebp-4],0
   return m;
   35. 00401088 mov eax,dword ptr [ebp-8]
  }
  36. 0040108B pop edi
  37. 0040108C pop esi
  38. 0040108D pop ebx
  39. 0040108E mov esp,ebp
  40. 00401090 pop ebp
  41. 00401091 ret
  分析上述反汇编代码,对于一个真正的程序员而言,将是一种很大的享受;而对于初学者,也将使其受益良多。所以请一定要赖着头皮认真研究,千万不要被吓倒!
  行1~10进行执行函数内代码的准备工作,保存现场。第2行对堆栈进行移动;第3行则意味着max函数为其内部局部变量准备的堆栈空间为50h字节;第11行表示把变量n的内存空间安排在了函数内部局部栈底减8的位置(占用4个字节)。
  第12~13行非常关键,对应着va_start ( ap, num ),这两行将第一个可变参数的地址赋值给了指针ap。另外,从第12行可以看出num的地址为ebp+0Ch;从第13行可以看出ap被分配在函数内部局部栈底减4的位置上(占用4个字节)。
  第22~27行最为关键,对应着va_arg (ap, int)。其中,22~24行的作用为将ap指向下一可变参数(可变参数的地址间隔为4个字节,从add eax,4可以看出);25~27行则取当前可变参数的值赋给变量t。这段反汇编很奇怪,它先移动可变参数指针,再在赋值指令里面回过头来取先前的参数值赋给t(从mov edx,dword ptr [ecx-4]语句可以看出)。Visual C++同学玩得有意思,不知道碰见同样的情况Visual Basic等其它同学怎么玩?
  第36~41行恢复现场和堆栈地址,执行函数返回操作。
  痛苦的反汇编之旅差不多结束了,看了这段反汇编我们总算弄明白了可变参数的存放位置以及它们被读取的方式,顿觉全省轻松!
  2、特殊的调用约定
  除此之外,我们需要了解C/C++函数调用对参数占用空间的一些特殊约定,因为在_cdecl调用协议中,有些变量类型是按照其它变量的尺寸入栈的。
  例如,字符型变量将被自动扩展为一个字的空间,因为入栈操作针对的是一个字。
  参数n实际占用的空间为( ( sizeof(n) + sizeof(int) - 1 ) & ~( sizeof(int) - 1 ) ),这就是第2.1节_INTSIZEOF(v)宏的来历!
  既然如此,前面给出的va_arg ( list, mode )宏为什么玩这么大的飞机就很清楚了。这个问题就留个读者您来分析.



http://wp1314.ycool.com/post.3001515.html

posted @ 2010-06-17 23:52 Brandon 阅读(1432) | 评论 (0)编辑 收藏

c++中string的一些转换

这是我上次从这里看到的把它给保存了下来,希望对你有用。 
1. c++中string到int的转换 
1) 在C标准库里面,使用atoi: 

#include <cstdlib> 
#include <string> 

std::string text = "152"; 
int number = std::atoi( text.c_str() ); 
if (errno == ERANGE) //可能是std::errno 

//number可能由于过大或过小而不能完全存储 

else if (errno == ????) 
//可能是EINVAL 

//不能转换成一个数字 


2) 在C++标准库里面,使用stringstream:(stringstream 可以用于各种数据类型之间的转换) 

#include <sstream> 
#include <string> 

std::string text = "152"; 
int number; 
std::stringstream ss; 


ss < < text;//可以是其他数据类型 
ss >> number; //string -> int 
if (! ss.good()) 

//错误发生 



ss < < number;// int->string 
string str = ss.str(); 
if (! ss.good()) 

//错误发生 


3) 在Boost库里面,使用lexical_cast: 

#include <boost/lexical_cast.hpp> 
#include <string> 

try 

std::string text = "152"; 
int number = boost::lexical_cast < int >( text ); 

catch( const boost::bad_lexical_cast & ) 

//转换失败 
}                      

2.string 转 CString 
CString.format(”%s”, string.c_str()); 
用c_str()确实比data()要好; 


3.char 转 CString 
CString.format(”%s”, char*); 

4.char 转 string 
string s(char *); 
只能初始化,在不是初始化的地方最好还是用assign(). 


5.string 转 char * 
char *p = string.c_str(); 

6.CString 转 string 
string s(CString.GetBuffer()); 
GetBuffer()后一定要ReleaseBuffer(),否则就没有释放缓冲区所占的空间. 

7.字符串的内容转换为字符数组和C—string 
(1)  data(),返回没有”\0“的字符串数组 
(2)  c_str(),返回有”\0“的字符串数组 
(3)  copy() 

8.CString与int、char*、char[100]之间的转换 

(1) CString互转int 

将字符转换为整数,可以使用atoi、_atoi64或atol。而将数字转换为CString变量,可以使用CString的Format函数。如 
CString s; 
int i = 64; 
s.Format(”%d”, i) 
Format函数的功能很强,值得你研究一下。 

void CStrDlg::OnButton1() 

  CString 
  ss=”1212.12″; 
  int temp=atoi(ss); 
  CString aa; 
  aa.Format(”%d”,temp); 
  AfxMessageBox(”var is ” + aa); 


(2) CString互转char* 

///char * TO cstring 
CString strtest; 
char * charpoint; 
charpoint=”give string a value”; //? 
strtest=charpoint; 

///cstring TO char * 
charpoint=strtest.GetBuffer(strtest.GetLength()); 

(3) 标准C里没有string,char *==char []==string, 可以用CString.Format(”%s”,char *)这个方法来将char *转成CString。 
    要把CString转成char *,用操作符(LPCSTR)CString就可以了。 
    CString转换 char[100] 
  char a[100]; 
  CString str(”aaaaaa”); 
  strncpy(a,(LPCTSTR)str,sizeof(a)); 

posted @ 2009-05-31 12:38 Brandon 阅读(426) | 评论 (0)编辑 收藏

lua的随机数问题

http://dev.csdn.net/author/yanjun_1982/b682d53ae78846a19eb0b7751a250750.html     

   也许很多人会奇怪为什么使用LUA的时候,第一个随机数总是固定,而且常常是最小的那个值,下面我就简要的说明一下吧,说得不好,还请谅解。我现在使用的4.0版本的LUA,看的代码是5.0的,呵呵

        LUA4.0版本中的自带函数库中有两个关于随机数的函数,一个是random,一个是randomseed。random有两个参数,用来设置随机数的范围,比如random(1,100)设置随机数的范围为1至100之间。由于C中所产生的随机序列是固定的,并且第一个随机数比较小,只有41。LUA重新设计了random函数,使得它可以产生范围固定的随机数,但由于LUA的random只是封装了C的rand函数,使得random函数也有一定的缺陷,那就是如果random的两个输入参数的值相差很小的时候,那么随机序列的第一个随机数就会和第一个输入参数很接近,比如第一次调用random(1,100)的时候,返回值肯定是1,只有相差大于799时,如random(1,800)第一次调用才会返回2,也是很接近1。
        由于这个原因,为了实现真正的随机,那么第一次就不能让玩家调用random函数,不然玩家就可以获得一些低概率的东西了。比如if random(1,100) == 1 then ...... do,看起来是1%的的概率,但是第一次执行的时候是100%成立的,存在一定的隐患。解决这个问题的方法有两个,一就是第一次random函数不能让玩家执行,二就是使用randomseed先设一个随机种子。对于第一种方法,可能还是有一定的风险,毕竟随机序列还是固定的,玩家第一次调用random的时候还是得到有规律的返回值。第二种方法比较安全,在服务器启动的时候设置一个随机种子,让系统产生的随机序列不相同,但使用randomseed的时候也还要注意一个问题,那就是做种子的数要足够的大,大于10000就行了。不然randomseed所产生的随机序列的第一个值还是很小。原因是randomseed是直接封装了C的srand,如果种子的值太小,那么srand所产生的序列和默认序列(srand(1)所产生的序列)是相差不大的,序列的第一个值还是很小。
        因此,只要在服务器启动的时候调用一下randomseed(GetTime())就可以解决这个问题了。
        还要补充一下,LUA中产生随机数的算法还是有一些问题,比如执行random(1,3276700),它返回的值最后两位必为0。这是由LUA本身的随机函数算法决定的。
        还是简要介绍一下LUA中random函数的实现方法吧,主要由源码中的下面两行实现:
        lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX;
        lua_pushnumber(L, (int)floor(r*(u-m+1))+m);
        其中m为random函数的第一个参数,u为第二个参数。由上面的代码可以看出,如果u-l太小,那么当r也很小的时候,r*(u-m+1)就会很小(小于1),那么再经过floor运算,最经结果就是m。这就可以解释为什么random产生的第一个随机数常常会很接近m。再来看看当m为0,u为327670的时候会怎样。在上面的代码里,RAND_MAX是一个宏,它的值是32767,也就是C语言中rand函数可以返回的最大值(不同的操作系统可能会有不一样的最大值)。当m为0,u为327670的时候,那么返回值就是floor(r*(327671)+0),我们再假设LUA与平台无关并且rand不会返回32767(上面用%避免了这个问题),那么r就可以简化为rand()/RAND_MAX,代入上式为floor(rand()*327671/32767)+0,就算rand()的返回值是32766,最终的结果也只有327660.99996......,经过floor运算后,最后那位数必为0。呵呵,我叫这样的随机数就伪随机数中的伪随机数。实际上面的公式是不允许化简的,即不能简单地把r代入r*(u-m+1),至于为什么,呵呵,因为r的值并不是rand()/RAND_MAX的值,r是double类型的,所以它只是一个和rand()/RAND_MAX很接近的数。 

        引用请注明出处。作者:yanjun_1982   日期:2006年10月11日

posted @ 2009-05-13 10:58 Brandon 阅读(2016) | 评论 (0)编辑 收藏

关于MFC画图的一些总结,MFC (Draw)

     摘要: 转自:http://www.cnblogs.com/volnet/articles/472794.html 首先对按下鼠标的一点进行记录,因此在WM_LBUTTONDOWN添加代码:void CDrawView::OnLButtonDown(UINT nFlags, CPoint point){     // TODO: ...  阅读全文

posted @ 2009-04-30 15:07 Brandon 阅读(5856) | 评论 (0)编辑 收藏

Lua string一些东西

一,函数
string.len(s)
string.find(s, sd, [index]) 返回i, j 表示 起始,终了位置索引。
string.sub
string.gsub
string.gfind
string.char
string.byte
string.format

二,字符类和模式修饰符
.      任意字符
%a     字母
%c     控制字符
%d     数字
%l     小写字母
%p     标点字符
%s     空白符
%u     大写字母
%w     字母和数字
%x     十六进制数字
%z     代表0的字符

模式修饰符有四个:
+      匹配前一字符1次或多次
*      匹配前一字符0次或多次
-      匹配前一字符0次或多次
?      匹配前一字符0次或1次

posted @ 2009-04-29 15:51 Brandon 阅读(1088) | 评论 (0)编辑 收藏

郁闷的一个问题

今天碰到个问题:
lua里在一个串里查找“(”怎么写,查找一个“)”可以写作i,j = string.find(s, ")");
而碰到“(”时是会出错的,怀疑一词法分析的时候一碰到“(”就压进栈了,然后因为括号不匹配报错? 
也不能对“(”提供转义

posted @ 2009-04-28 18:14 Brandon 阅读(199) | 评论 (0)编辑 收藏

泛型算法

拷贝:

copy()
reverse_copy()
rotate_copy()
remove_copy()  拷贝不等于某值的元素到另一个序列。
remove_copy_if() 拷贝符合条件的到另一个序列。

填充和生成:
fill()
fill_n() 填充序列中的n个元素。
generate()为序列中的每个元素调用gen()函数。

排列:
next_permuttion() 后一个排列。
prev_permutation()

partition() 划分,将满足条件的元素移动到序列的前面。
stable_partition()

查找和替换:
find()
binary_search() 在一个已经有顺序的序列上查找。
find_if()
search() 检查第二个序列是否在第一个序列中出现,且顺序相同。

删除:注意必须调用erase()来真正删除
remove()
unique()删除相邻重复元素,最好现排序。

合并序列:
merge()

数值算法:
accumulate() 对序列的每个元素进行运算后求和。
transform() 也可以对每个元素进行运算。
计数:
size()总个数。
count()等于某值的元素个数。

adjacent_difference 序列中的后一个减前与他相邻的前一个得到新的序列。

adiacent_find

 accumlate  iterator 对标志的序列中的元素之和,加到一个由 init 指定的初始值上。重载的版本不再做加法,而是传进来的二元操作符被应用到元素上。 

adjacent_different :创建一个新序列,该序列的每个新值都代表了当前元素与上一个元素的差。重载版本用指定的二元操作计算相邻元素的差。 
adjacent_find 
:在 iterator 对标志的元素范围内,查找一对相邻的重复元素,如果找到返回一个 ForwardIterator ,指向这对元素的第一个元素。否则返回 last 。重载版本使用输入的二元操作符代替相等的判断。 
binary_search 
:在有序序列中查找 value ,如果找到返回 true 。重载的版本使用指定的比较函数对象或者函数指针来判断相等。 
copy 
:复制序列。 
copy_backward 
:除了元素以相反的顺序被拷贝外,别的和 copy 相同。 
count 
:利用等于操作符,把标志范围类的元素与输入的值进行比较,并返回相等元素的个数。 
count_if 
:对于标志范围类的元素,应用输入的操作符,并返回结果为 true 的次数。 
equal 
:如果两个序列在范围内的元素都相等,则 equal 返回 true 。重载版本使用输入的操作符代替了默认的等于操作符。 
equal_range 
:返回一对 iterator ,第一个 iterator 表示由 lower_bound 返回的 iterator ,第二个表示由 upper_bound 返回的 iterator值。 
fill 
:将输入的值的拷贝赋给范围内的每个元素。 
fill_n 
:将输入的值赋值给 first  frist+n 范围内的元素。 
find 
:利用底层元素的等于操作符,对范围内的元素与输入的值进行比较。当匹配时,结束搜索,返回该元素的一个 InputIterator  
find_if 
:使用输入的函数替代了等于操作符执行了 find  
find_end 
:在范围内查找“由输入的另外一个 iterator 对标志的第二个序列”的最后一次出现。重载版本中使用了用户输入的操作符替代等于操作。 
find_first_of 
:在范围内查找“由输入的另外一个 iterator 对标志的第二个序列”中的任意一个元素的第一次出现。重载版本中使用了用户自定义的操作符。 
for_each 
:依次对范围内的所有元素执行输入的函数。 
generate 
:通过对输入的函数 gen 的连续调用来填充指定的范围。 
generate_n 
:填充 n 个元素。 
includes 
:判断 [first1, last1) 的一个元素是否被包含在另外一个序列中。使用底层元素的 <= 操作符,重载版本使用用户输入的函数。 
inner_product 
:对两个序列做内积 ( 对应的元素相乘,再求和 ) ,并将内积加到一个输入的的初始值上。重载版本使用了用户定义的操作。 
inner_merge 
:合并两个排过序的连续序列,结果序列覆盖了两端范围,重载版本使用输入的操作进行排序。 
iter_swap 
:交换两个 ForwardIterator 的值。 
lexicographical_compare 
:比较两个序列。重载版本使用了用户自定义的比较操作。 
lower_bound 
:返回一个 iterator ,它指向在范围内的有序序列中可以插入指定值而不破坏容器顺序的第一个位置。重载函数使用了自定义的比较操作。 
max 
:返回两个元素中的较大的一个,重载版本使用了自定义的比较操作。 
max_element 
:返回一个 iterator ,指出序列中最大的元素。重载版本使用自定义的比较操作。 
min 
:两个元素中的较小者。重载版本使用自定义的比较操作。 
min_element 
:类似与 max_element ,不过返回最小的元素。 
merge 
:合并两个有序序列,并存放到另外一个序列中。重载版本使用自定义的比较。 
mismatch 
:并行的比较两个序列,指出第一个不匹配的位置,它返回一对 iterator ,标志第一个不匹配的元素位置。如果都匹配,返回每个容器的 last 。重载版本使用自定义的比较操作。 
next_permutation 
:取出当前范围内的排列,并将其重新排序为下一个排列。重载版本使用自定义的比较操作。 
nth_element 
:将范围内的序列重新排序,使所有小于第 n 个元素的元素都出现在它前面,而大于它的都出现在后面,重载版本使用了自定义的比较操作。 
partial_sort 
:对整个序列做部分排序,被排序元素的个数正好可以被放到范围内。重载版本使用自定义的比较操作。 
partial_sort_copy 
:与 partial_sort 相同,除了将经过排序的序列复制到另外一个容器。 
partial_sum 
:创建一个新的元素序列,其中每个元素的值代表了范围内该位置之前所有元素之和。重载版本使用了自定义操作替代加法。 
partition 
:对范围内元素重新排序,使用输入的函数,把计算结果为 true 的元素都放在结果为 false 的元素之前。 
prev_permutation 
:取出范围内的序列并将它重新排序为上一个序列。如果不存在上一个序列则返回 false 。重载版本使用自定义的比较操作。 
random_shuffle 
:对范围内的元素随机调整次序。重载版本输入一个随机数产生操作。 
remove 
:删除在范围内的所有等于指定的元素,注意,该函数并不真正删除元素。内置数组不适合使用 remove  remove_if 函数。 
remove_copy 
:将所有不匹配的元素都复制到一个指定容器,返回的 OutputIterator 指向被拷贝的末元素的下一个位置。 
remove_if 
:删除所有范围内输入操作结果为 true 的元素。 
remove_copy_if 
:将所有不匹配的元素拷贝到一个指定容器。 
replace 
:将范围内的所有等于 old_value 的元素都用 new_value 替代。 
replace_copy 
:与 replace 类似,不过将结果写入另外一个容器。 
replace_if 
:将范围内的所有操作结果为 true 的元素用新值替代。 
replace_copy_if 
:类似与 replace_if ,不过将结果写入另外一个容器。 
reverse 
:将范围内元素重新按反序排列。 
reverse_copy 
:类似与 reverse ,不过将结果写入另外一个容器。 
rotate 
:将范围内的元素移到容器末尾,由 middle 指向的元素成为容器第一个元素。 
rotate_copy 
:类似与 rotate ,不过将结果写入另外一个容器。 
search 
:给出了两个范围,返回一个 iterator ,指向在范围内第一次出现子序列的位置。重载版本使用自定义的比较操作。 
search_n 
:在范围内查找 value 出现 n 次的子序列。重载版本使用自定义的比较操作。 
set_difference 
:构造一个排过序的序列,其中的元素出现在第一个序列中,但是不包含在第二个序列中。重载版本使用自定义的比较操作。 
set_intersection 
:构造一个排过序的序列,其中的元素在两个序列中都存在。重载版本使用自定义的比较操作。 
set_symmetric_difference 
:构造一个排过序的序列,其中的元素在第一个序列中出现,但是不出现在第二个序列中。重载版本使用自定义的比较操作。 
set_union 
:构造一个排过序的序列,它包含两个序列中的所有的不重复元素。重载版本使用自定义的比较操作。 
sort 
:以升序重新排列范围内的元素,重载版本使用了自定义的比较操作。 
stable_partition 
:与 partition 类似,不过它不保证保留容器中的相对顺序。 
stable_sort 
:类似与 sort ,不过保留相等元素之间的顺序关系。 
swap 
:交换存储在两个对象中的值。 
swap_range 
:将在范围内的元素与另外一个序列的元素值进行交换。 
transform 
:将输入的操作作用在范围内的每个元素上,并产生一个新的序列。重载版本将操作作用在一对元素上,另外一个元素来自输入的另外一个序列。结果输出到指定的容器。 
unique 
:清除序列中重复的元素,和 remove 类似,它也不能真正的删除元素。重载版本使用了自定义的操作。 
unique_copy 
:类似与 unique ,不过它把结果输出到另外一个容器。 
upper_bound 
:返回一个 iterator ,它指向在范围内的有序序列中插入 value 而不破坏容器顺序的最后一个位置,该位置标志了一个大于 value 的值。重载版本使用了输入的比较操作。 
堆算法: C++ 标准库提供的是 max-heap 。一共由以下 4 个泛型堆算法。 
make_heap 
:把范围内的元素生成一个堆。重载版本使用自定义的比较操作。 
pop_heap 
:并不是真正的把最大元素从堆中弹出,而是重新排序堆。它把 first  last-1 交换,然后重新做成一个堆。可以使用容器的 back 来访问被“弹出“的元素或者使用 pop_back 来真正的删除。重载版本使用自定义的比较操作。 
push_heap 
:假设 first  last-1 是一个有效的堆,要被加入堆的元素在位置 last-1 ,重新生成堆。在指向该函数前,必须先把元素插入容器后。重载版本使用指定的比较。 
sort_heap 
:对范围内的序列重新排序,它假设该序列是个有序的堆。重载版本使用自定义的比较操作。

posted @ 2009-04-10 12:27 Brandon 阅读(397) | 评论 (0)编辑 收藏

详解typedef 的用法

     摘要: typedef 的用法 --摘自一位cnblog的一位大侠 用途一: 定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如: char* pa, pb;  // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针, // 和一个字符变量; 以下则可行: t...  阅读全文

posted @ 2009-04-10 09:20 Brandon 阅读(230) | 评论 (0)编辑 收藏

CEdit里的光标

int   nStart,   nEnd;   

m_edit1.GetSel(nStart,   nEnd);   

if(nStart   ==   nEnd)   
{   
    m_edit1.SetSel(nStart
+1,   nEnd+1);       
}
CEdit不支持SetCaretPos()详见:http://support.microsoft.com/default.aspx?scid=kb;en-us;259949#appliesto

posted @ 2009-04-03 11:14 Brandon 阅读(1301) | 评论 (0)编辑 收藏

仅列出标题
共2页: 1 2 
<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

导航

统计

常用链接

留言簿(2)

随笔分类

随笔档案

文章分类

文章档案

收藏夹

IT WEB

常用链接

牛人BLOG

学习网站

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜