可变参数函数实现

原创 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语言中可变参数函数实现原理

C语言中可变参数函数实现原理
  • Li_Ning_
  • Li_Ning_
  • 2016年06月02日 23:52
  • 2816

如何实现函数来处理可变参数

使用va宏实现具有可变参数的函数
  • sunny_ss12
  • sunny_ss12
  • 2016年04月02日 02:32
  • 1412

C++可变参数函数

C++可变参数函数的三种实现方法:C风格实现,基于initializer_list,和基于模板。三种实现方法的比较和优缺点总结。...
  • qq_35280514
  • qq_35280514
  • 2016年06月11日 20:26
  • 5202

使用可变参数,实现函数,求函数参数的平均值和最大值

利用可变参数列表:可变参数列表是通过宏来实现的,这些宏定义在stdarg.h中。 值得注意的是:可变参数列表传参时,第一个传的参数的个数。 步骤: 1.定义一个va_list类型的变...
  • sherry_zhe
  • sherry_zhe
  • 2017年11月16日 14:12
  • 157

C++之可变参数的实现简介

由于在C语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦,即使采用C++,如果参数个数不能确定,也很难采用函数重载。对这种情况,提出了指针参数来解决问题。   如printf()函数,其...
  • sauphy
  • sauphy
  • 2015年12月14日 22:10
  • 1029

[C语言]利用可变参数列表求平均值。

题目:设计一个函数可以求任意多个数的平均值(使用可变参数列表) 思路: 利用可变参数列表即可,可变参数列表是通过宏来实现的,这些宏定义在stdarg.h中。注意的是可变参数列表传参时,第一个传的参...
  • qq1010234991
  • qq1010234991
  • 2016年06月08日 00:16
  • 358

C语言可变参数函数的使用方法讲解

http://blog.chinaunix.net/uid-21736802-id-1817658.html 本文主要介绍可变参数的函数使用,然后分析它的原理,程序员自己如何对它们实现和封装...
  • zangchaodotcnatgmail
  • zangchaodotcnatgmail
  • 2015年12月28日 11:02
  • 1107

php 函数使用可变数量的参数

php在用户自定义函数中支持可变数量的参数列表。本文将介绍php函数使用可变数量的参数的方法,并提供兼容不同php版本的完整演示实例。...
  • fdipzone
  • fdipzone
  • 2017年04月23日 17:27
  • 22314

利用可变模板参数实现log功能

在以前的博文中,写过类似的课题。使用的是下面这种方法。// 递归出口 template void logOld(const T& t) { std::cout ...
  • zhx6044
  • zhx6044
  • 2016年03月19日 17:10
  • 2188

可变宏参数个数和可变函数参数个数

可变宏参数个数和可变函数参数个数
  • RHEL_admin
  • RHEL_admin
  • 2015年02月13日 19:31
  • 917
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:可变参数函数实现
举报原因:
原因补充:

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