关闭

探秘C<stdarg.h>实现自己的printf( )

147人阅读 评论(0) 收藏 举报

前言

最近一直再看C标准库的东西,今天看到了<stdarg.h> .

__START

C语言由一个非常强大的功能,就是它允许定义可接受一个可变参数列表的函数。

尽管C为了这个可变参数也一直在修改着自身的东西,现在基本稳定,现阶段主要是有一下一些宏来确定这些东西。

va_start  : 一个函数必须至少声明一个固定的参数。这个宏引用了最后一个固定参数所以它能够对可变参数表进行定位。

va_arg : 不能在va_arg 中没有函数原型的情况下指定会被类型提升的参数类型。例如,必须使用double 来代替flaot .这些宏不能复制适用于可变参数表的改变参数类型的规则。

va_end: 一个函数在返回到它的调用者之前一定要调用va_end .这是因为某些实现在函数返回之前需要整理控制信息。

详细来看看这几个宏的用法

va_list :

这个宏其实就是void * .在VS下却被实现为char * 类型。看来不同的编译环境下还是有区别的。

va_start:

#include<stdarg.h>

void va_start (va-list ap,parmN);

 

在访问所有未命名的参数之前调用va_start.宏va_start 对ap 进行初始化,以便后面va_arg 和va_end 对它的使用。

 

va_arg:

这个宏被展开成一个包含类型为type,值为ap的表达式。参数ap应该首先被宏va_start 或 va_copy初始化,但又必须在被宏va_end调用之前使用。每次调用va_arg都会改变ap值使得后续的参数值能被依次添加。参数type应该是一个类型名,并且用type*能够得到该类型的指针类型。如果type为空,或者type和实际参数不匹配, 那么除了以下两种情况,这个宏的行为是未定义的。
1. 一个是带符号整型,另一个是与之对应的无符号整型,并且值可以被表达成这两种类型的任何一种;
2. 一个是空类型指针,另一个是字符类型指针。

简而言之就是,每次像后偏移一个参数变量。

va_end:

函数完成清空这个可变参数指针变量。

再来看看这几个宏的实现

首先来看看几个需要用到的宏:

#define _ADDRESSOF (v)    (&reinterpret_cast<const char &>(v) )

这个宏用来获取变量V 在函数栈中的地址

typedef  char * va_list  这里实现成为char *完全是为了指针运算方便,也有实现为void * 的,但是运算的时候仍然需转换为char *.所以我们就讨论char *这种情况。

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

以int所占的字节数来对齐操作,如果int占四字节则以4字节为对齐为标准读取数据.

va_start:

#define va_start(ap,v)      (ap = (va_list)_ADDRESSOF(v) + _INITSIZEOF(v))

它的第一个参数是指向可变参数字符串的变量,第二个参数是可变参数函数的第一个参数,通常是可变参数列表中的参数。

va_arg:

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

注意这里的   "ap += "  所以这里其实是修改了ap 的值,使它指向像一个参数,后边的  -_INTSIZEOF()” 又加回了这个偏移量只是为了我们可以通过它获得当前的指针位置罢了。我们的ap 指针依然是变化了。

va_end:

#define    va_arg ( ap )         (ap = (va_list ) 0)

清零可变参数指针。

[c]

#include<stdio.h>
#include<stdarg.h>

void print(char *fmt,...){
char *vaptr = NULL;
char *temp = NULL;
int count = 0;
int i = 0;
va_list ap;
va_start(ap,fmt);
temp = fmt;
while(*temp != '\0'){
if(*temp == '%'){
switch(*(++temp)){
case 's':
vaptr = va_arg(ap,char *);
puts(vaptr);
break;
case 'c':
vaptr = va_arg(ap,int);
putchar(vaptr);
break;
case 'd':
vaptr = va_arg(ap,int);
printint(vaptr);
break;
default:
break;
}
}
}
}

int main(){

char test[20] = "hello world";
print("%s",test);

return 0;
}

[/c]

 这个程序只是简单的实现了printf,远远不能和glibc 库的printf 相比,但是我们只需要知道原理就好。

关于<stdarg.h>的使用

C 标准库的<stdarg.h>  函数给我们提供了这这几个有用的宏,使我们可以写变参函数,但是我们需要了解这些宏的使用规则。

1。必须明确的说明一个函数具有一个可变参数链表。这就意味着它的参数表一定以省略(,...)来作为参数列表,在定义与声明中均是如此。

2. 声明函数时必须至少含有一个固定的参数,最后一个固定参数引用时习惯上称为parmN.

3.必须声明一个va_lsit 类型的数据对象,习惯上成为ap.当然这个数据对象在函数内一定是可见的。

4.必须才函数内执行va_start ,在此之后才能执行va_list ,va_arg 或者 va_end.

5.在使用va_arg  必须为每一个参数按照他们在函数调用中出现的顺序指定适当的类型。

 

查看原文:http://zmrlinux.com/2015/11/21/%e6%8e%a2%e7%a7%98c%e5%ae%9e%e7%8e%b0%e8%87%aa%e5%b7%b1%e7%9a%84printf/

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:68077次
    • 积分:3058
    • 等级:
    • 排名:第11382名
    • 原创:244篇
    • 转载:6篇
    • 译文:0篇
    • 评论:13条
    最新评论