您的位置:逆风者 VC++ 正文
 添加时间:2008-07-13 原文发表:2008-07-13 人气:74 来源:vckbase.net

本文章共3320字,分3页,当前第1页,快速翻页:
 

作者:戎亚新

摘要:一直以来都觉得printf似乎是c语言库中功能最强大的函数之一,不仅因为它能格式化输出,更在于它的参数个数没有限制,要几个就给几个,来者不拒。printf这种对参数个数和参数类型的强大适应性,让人产生了对它进行探索的浓厚兴趣。

逆风编程精品

关键字:printf, 可变参数

1. 使用情形

int a =10;
double b = 20.0;
char *str = "Hello world";
printf("begin print\n");
printf("a=%d, b=%.3f, str=%s\n", a, b, str);
...
  从printf的使用情况来看,我们不难发现一个规律,就是无论其可变的参数有多少个,printf的第一个参数总是一个字符串。而正是这第一个参数,使得它可以确认后面还有有多少个参数尾随。而尾随的每个参数占用的栈空间大小又是通过第一个格式字符串确定的。然而printf到底是怎样取第一个参数后面的参数值的呢,请看如下代码

2. printf 函数的实现

//acenv.h
typedef char *va_list;

#define  _AUPBND        (sizeof (acpi_native_int) - 1)
#define  _ADNBND        (sizeof (acpi_native_int) - 1)
                        
#define _bnd(X, bnd)    (((sizeof (X)) + (bnd)) & (~(bnd)))
#define va_arg(ap, T)   (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))
#define va_end(ap)      (void) 0
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))

//start.c
static char sprint_buf[1024];
int printf(char *fmt, ...)
{
	va_list args;
	int n;
	va_start(args, fmt);
	n = vsprintf(sprint_buf, fmt, args);
	va_end(args);
	write(stdout, sprint_buf, n);
	return n;
}

//unistd.h
static inline long write(int fd, const char *buf, off_t count)
{
	return sys_write(fd, buf, count);
}
3. 分析

  从上面的代码来看,printf似乎并不复杂,它通过一个宏va_start把所有的可变参数放到了由args指向的一块内存中,然后再调用vsprintf. 真正的参数个数以及格式的确定是在vsprintf搞定的了。由于vsprintf的代码比较复杂,也不是我们这里要讨论的重点,所以下面就不再列出了。我们这里要讨论的重点是va_start(ap, A)宏的实现,它对定位从参数A后面的参数有重大的制导意义。现在把 #define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND)))) 的含义解释一下如下:

    va_start(ap, A)
    {
         char *ap =  ((char *)(&A)) + sizeof(A)并int类型大小地址对齐
    }
  在printf的va_start(args, fmt)中,fmt的类型为char *, 因此对于一个32为系统 sizeof(char *) = 4, 如果int大小也是32,则va_start(args, fmt);相当于 char *args = (char *)(&fmt) + 4; 此时args的值正好为fmt后第一个参数的地址。对于如下的可变参数函数
    void fun(double d,...)
    {
        va_list args;
    	int n;
    	va_start(args, d);
    }
则 va_start(args, d);相当于
    char *args = (char *)&d + sizeof(double);
  此时args正好指向d后面的第一个参数。

  可变参数函数的实现与函数调用的栈结构有关,正常情况下c/c++的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈。对于函数

    void fun(int a, int b, int c)
    {
        int d;
        ...
    }
其栈结构为
    0x1ffc-->d
    0x2000-->a
    0x2004-->b
    0x2008-->c
  对于任何编译器,每个栈单元的大小都是sizeof(int), 而函数的每个参数都至少要占一个栈单元大小,如函数 void fun1(char a, int b, double c, short d) 对一个32的系统其栈的结构就是
 
本文章更多内容1 - 2 - 3 - 下一页>>
相关文章

通用 Thunk
VC6绘制3D饼状图 - 完美版
自定义控件—特效列表控件
直接调用类成员函数地址
Thunk 技术的一个改进
一个工业控制管道流动控件的实现
在C++中实现C#的delegate机制
强制编译时约束
VC6绘制3D饼状图
使用SNMP编辑(添加/删除/修改)ARP表
内存映射修改大文件
一个简单的外挂制作
动态加载CBitmapButton的四副状态图
一个支持各种交叉等形状工业控制管道控件的
利用模板元编程实现解循环优化
单件模式之土著人的可乐瓶
通过ODBC API实现对数据库的访问
七段数码显示的数字时钟
仿QQ悬挂窗口的实现
VC模仿超炫QQ界面的实现

相关评论


本文章所属分类:首页 VC++

  热门关键字: