1.Printf系列函数 (摘自陈珍敬的CSDN博客)
Printf 函数是一组函数的总称,包含:
#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);
#include <stdarg.h>
int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
注:这组函数使用可变函数列表作为参数。还包括间接使用 printf 的函数,如 wxWidget 中的 wxLogXXX 等。
Printf 函数的 format 参数 使用 % 作为特殊字符用以定义所要打印的串的特殊格式;
下面一种格式的printf(#include <stdarg.h> )函数需要用到以va开头的宏,在这边介绍一下:
2.va开头的宏
typedef char * va_list;
va_star
#define va_start _crt_va_start
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )//上对齐
va_end
#define va_end _crt_va_end
#define _crt_va_end(ap) ( ap = (va_list)0 )
va_arg
#define va_arg _crt_va_arg
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
函数参数的传递原理:函数参数是以数据结构 : 栈的形式存取 (C 语言在函数调用时,先将最后一个参数压入栈 ) 。在 stdcall 下,堆栈中 , 各个参数的分布情况是倒序的 . 即最后一个参数在列表中地址最高部分 , 第一个参数在列表地址的最低部分 . 参数在堆栈中的分布情况如下 :
最后一个参数
倒数第二个参数
...
第一个参数
函数返回地址
函数代码段
获取省略号指定的参数:在函数体中声明一个 va_list ,然后用 va_start 函数来获取参数列表中的参数,使用完毕后调用 va_end() 结束。举个例子 (VS) :
void TestFun(char* pszDest, int DestLen, const char* pszFormat, ...)
{
va_list args;
va_start(args, pszFormat); // pszFormat 参数下一个参数的地址 , 即第一个可变参数地址 .
_vsnprintf(pszDest, DestLen, pszFormat, args);
va_end(args);
}
原理: va_start 使 argp 指向第一个可选参数。 va_arg 返回参数列表中的当前参数并使 argp 指向参数列表中的下一个参数。 va_end 把 argp 指针清为 NULL 。函数体内可以多次遍历这些参数,但是都必须以 va_start 开始,并以 va_end 结尾。
va_start, va_arg 和 va_end 仅仅是一些宏 , 用于获取参数堆栈地。
摘自: wchar.h
#ifdef _M_CEE_PURE
typedef System::ArgIterator va_list;
#else
typedef char * va_list;
#endif
注 :char 型指针的特点是 ++ 、 -- 操作对其作用的结果是增 1 和减 1 (因为 sizeof(char) 为 1 )
摘自 stdarg.h
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end
摘自 *vadefs.h - defines helper macros for stdarg.h
/* A guess at the proper definitions for other platforms */( 不同平台定义不同,指针是依赖平台的 )
#ifdef __cplusplus
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
#else
#define _ADDRESSOF(v) ( &(v) )
#endif
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) //4 的倍数
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define _crt_va_end(ap) ( ap = (va_list)0 )
注: _INTSIZEOF(n) 宏是为了考虑那些内存地址需要对齐的系统,从宏的名字来应该是跟 sizeof(int) 对齐。一般的 sizeof(int)= 4 ,也就是参数在内存中的地址都为 4 的倍数。比如,如果 sizeof(n) 在 1 - 4 之间,那么 _INTSIZEOF(n) = 4 ;如果 sizeof(n) 在 5 - 8 之间,那么 _INTSIZEOF(n)=8 。具体细节可以参见:数据在机器内的存储与运算章节。
va_arg(ap,t) 得到第一个参数的值 ( 即调用前 ap 的值 ), 并且将 ap 指针上移一个 _INTSIZEOF(t), 即指向下一个可变参数的地址。这个宏分两步: 1) (ap += _INTSIZEOF(t)) 移动指针地址 ; 2) 使用移动后的 ap 值计算原先的参数值 ( 减掉移动值 _INTSIZEOF(t)) 。
如何直接只用可变参数列表:
虽然可以通过在堆栈中遍历参数列表来读出所有的可变参数 , 但是想直接使用可变参数列表,还需要知道可变参数的个数 , 以便结束遍历。解决办法 : a. 参数指定 : 在第一个起始参数中指定参数个数 , 那么就可以在循环还中读取所有的可变参数 ; b. 位哨 : 定义一个结束标记 , 在调用函数的时候 , 在最后一个参数中传递这个标记 , 这样在遍历可变参数的时候 , 可以根据这个标记结束可变参数的遍历。举例:
void arg_cnt(int cnt, ...)
{
int value=0;
int i=0;
int arg_cnt=cnt;
va_list arg_ptr;
va_start(arg_ptr, cnt);
for(i = 0; i < cnt; i++)
{
value = va_arg(arg_ptr,int);
printf("value%d=%d/n", i+1, value);
}
}