可变参数函数实现

原创 2007年10月08日 16:19:00


void ErrorMsg(const char *pszParam, ...)
{
 char buf[1024];

 va_list va;
 va_start(va, pszParam);
 vsprintf(buf, pszParam, (va_list)va);
 va_end(va);


 SendDlgItemMessage( g_MainHwnd,  IDC_MESSAGE_LIST, LB_ADDSTRING, 0,  (LPARAM)buf );
 TRACE("(X) Error[%s] /n", buf) ;

}

====================================================================

网上看到有人问C下的printf函数怎样实现,觉得这个问题有点意思,于是找了下printf函数的源代码.

printf的声明如下:
    int __cdecl printf(const char *format, ...);
实现部分为先分析输出格式串,计算出后面参数的个数,接着依次输出后面的参数值到电脑终
端.我觉得printf函数之所以神秘,是因为我们基本上没有写过可变参数的函数,如果掌握了
可变参数的"秘密"之后,就与其它函数没有太大的区别了.
1.调用约定及堆栈图
在讲解可变参数之前,我们有必要了解参数是传递的.调用约定(calling conversion)说
的就是这个事情.每一种调用约定对应一种参数传递方式,各种调用约定的特性见下表:
 
调用约定
(Calling Conversion)
参数传递
(Argument Passing)
栈维护
(Stack Maintenance)
名称修饰
(Name Decoration)
备注(Notes)
__cdecl
从右到左
调用者清除栈参数.唯一允许可变参数的方式
函数名前加下划线,
如_Foo
C和C++默认的方式
__stdcall
从右到左
被调用者清除栈参数
函数名前加下划线,函数名后加@以及十进制表示的参数所占总字节数,如_Foo@12
几乎所有的系统函数都采用这种方式;VB内部函数也是这种方式
__fastcall
头两个DWORD参数通过ECX和EDX传递;剩余的从右到左传递.
调用者清除栈参数.
函数名前后都加@,并在后面跟十进制表示的参数所占字节数.
只在Intel cpu上才能够使用.Delphi编译器就采用这种方式.
This
右到左.参数this通过ECX寄存器传递.
调用者清除栈参数
None
在没有指定标准调用(__stdcall)的方式下C++的类方法调用就是这种情况.COM的方法都被声明为标准调用方式
Naked
从右到左
调用者清除栈参数.
None
VxD使用这种方式,或者你不想要prolog和epilog时采用.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 















printf采用的就是__cdecl方式,在__cdecl下参数都是通过栈来传递,是一种后进先出的数
据结构,通常从高地址开始存放数据,函数栈结构有如下这个样子:
   
    [参数 n       ]
    ...
    [参数 2       ]
    [参数 1       ]
    [函数返回地址 ]
    [前基地址指针 ]
    [局部变量     ]
 
我们用一个例子来说明:
 
 void Foo(int a, int b)
    {
        DWORD MyArray[4];
        int Index;   
    }
   
    void main(void)
    {
        Foo(3, 4);
        int iCount = 1;
    }
 
 
当程序从main进入到Foo时,栈结构图如下
    [4                     ]    /*参数b的值*/
    [3                     ]    /*参数a的值*/
    [返回地址        ]    /*main中代码int iCount = 1;的地址*/
    [前基地址值    ]    /*ebp*/
    [MyArray[3]      ]
    [MyArray[2]      ]
    [MyArray[1]      ]
    [MyArray[0]      ]
    [Index              ]    /*Foo中的局部变量Index*/
 
由于栈中数据存放是从高到低的原则,如果我们知道参数a的地址为0x0012ff24,则参数b的地址为:
    &b = 0x0012ff24 + sizeof(a);
懂得了怎样通过一个参数地址得到另一个参数的地址,我们就已经具备了处理可变参数的能力了.
 
2.
    为了让处理可变参数的过程更直观、不易出错,我们通常都会看到可变参数的函数中对
如下几个宏的使用,宏及其定义如下(摘自VC6中的STDARG.H):
typedef char * va_list;
 
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v)     ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t)      ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap)       ( ap = (va_list)0 )
 
 
这几个宏应该算比较好理解,
    _INTSIZEOF(n)   计算n的字节大小,以int所占字节数作为对齐.
    va_start(ap,v)      让ap指向参数v的下一个参数.
    va_arg(ap,t)        得到ap所指向的值,并让ap指向下一个参数.
    va_end(ap)       让ap = 0.
   
3.例子
 一个例子可以让我们对这些宏有很好的掌握,下例来自MSDN
 
#include <malloc.h>
#include <stdio.h>
#include <string.h>
 
// crt_va.c
/* The program below illustrates passing a variable
 * number of arguments using the following macros:
 *      va_start            va_arg              va_end
 *      va_list             va_dcl (UNIX only)
 */
 
#include <stdio.h>
#include <stdarg.h>
int average( int first, ... );
 
int main( void )
{
       /* Call with 3 integers (-1 is used as terminator). */
       printf( "Average is: %d/n", average( 2, 3, 4, -1 ) );
      
       /* Call with 4 integers. */
       printf( "Average is: %d/n", average( 5, 7, 9, 11, -1 ) );
      
       /* Call with just -1 terminator. */
       printf( "Average is: %d/n", average( -1 ) );
}
 
/* Returns the average of a variable list of integers. */
int average( int first, ... )
{
       int count = 0, sum = 0, i = first;
       va_list marker;
      
       va_start( marker, first );     /* Initialize variable arguments. */
       while( i != -1 )
       {
              sum += i;
              count++;
              i = va_arg( marker, int);
       }
       va_end( marker );              /* Reset variable arguments.      */
       return( sum ? (sum / count) : 0 );
}
 
 
4.引用资源列表
    通过下面的书籍或文章可以找到相关的更多信息:
    http://www.codeproject.com/debug/cdbntsd2.asp
    缓冲区溢出的原理和实践(Phrack) by Sinbad
<<Debugging Applications>> Chapter by John Robbins
 
 

C语言中可变参数函数实现原理浅析

1、C函数调用的栈结构   可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈。例如,对于函数:  ...
  • zhangpinghao
  • zhangpinghao
  • 2013-09-04 15:31:27
  • 575

C语言实现可变参函数工作原理

/* * 文件名: variableArgument.c * 功能描述: 实现可变参函数工作过程原理 * 编写人: 王廷云 * 编写时间: 2017-10-13 */ #include &a...
  • aiwangtingyun
  • aiwangtingyun
  • 2018-03-16 09:37:40
  • 7

delphi使用可变参数函数的方法 (2012-10-08 12:47:01)

delphi使用可变参数函数的方法  (2012-10-08 12:47:01) 转载▼ 标签:  杂谈 分类: 杯男日记 貌似...
  • autumn20080101
  • autumn20080101
  • 2012-12-04 11:28:04
  • 650

可变参数函数实现

void ErrorMsg(const char *pszParam, ...){ char buf[1024]; va_list va;  va_start(va, pszParam);  vspr...
  • hbyh
  • hbyh
  • 2007-10-08 16:19:00
  • 381

可变参数 不定参数的编写

std::string re; va_list vagrc; va_start(vagrc,lpszFormat); int nSize = 4096; re.resize(nSize); ...
  • u012607841
  • u012607841
  • 2017-01-13 01:10:56
  • 295

在调用支持可变参数格式化的函数时的一个很掩蔽问题

在调用支持可变参数的函数时的一个很掩蔽的问题
  • chenlycly
  • chenlycly
  • 2014-06-21 13:05:20
  • 766

C语言可变参数函数实现

c语言实现函数可变参数   ...
  • jinkui2008
  • jinkui2008
  • 2007-12-25 20:28:00
  • 2969

参数可变函数的实现(上)

 此文献给如我一般还在探索C语言之路的朋友们。 注:本文中测试程序的编译环境为win2000和VC6.0缘起:作为一个程序员,我没有写过参数可变的函数,我相信大部分朋友也没有涉及过,或者我的境界层次太...
  • ZhouHM
  • ZhouHM
  • 2004-04-07 22:24:00
  • 4255

c/c++支持可变参数的函数

一、为什么要使用可变参数的函数?    一般我们编程的时候,函数中形式参数的数目通常是确定的,在调用时要依次给出与形式参数对应的所有实际参数。但在某些情况下希望函数的参数个数可以根据需要确定,因此c语...
  • xiven
  • xiven
  • 2009-07-01 19:09:00
  • 498

可变参数函数实现

sunny sunny 4 141 2007-03-15T06:52:00Z 2007-03-15T06:57:00Z 1 526 3000 sonix 25 7 351...
  • swimmer2000
  • swimmer2000
  • 2007-03-15 14:59:00
  • 1495
收藏助手
不良信息举报
您举报文章:可变参数函数实现
举报原因:
原因补充:

(最多只允许输入30个字)