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

原创 2015年11月21日 16:40:31

前言

最近一直再看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/

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

C/C++中stdarg.h或cstdarg编写如printf一样参数可以是变长的任意类型和任意个数的函数

stdarg.h  stdarg.h是C语言中C标准函式库的标头档,stdarg是由standard(标准) arguments(参数)简化而来,主要目的为让函式能够接收不定量参数。[1] C++的c...

C库<stdarg.h>实现可变参函数

我们都知道C++里的函数有重载功能,对同名函数可以传入不同类型或个数的参数,实现不同的功能。这些参数类型和个数必须在函数定义时确定的,编译器会根据参数列表格式,调用相应的函数。 但是,如果我们在调用...

printf()的Windows实现探秘及如何在控制台显示24位真彩色

prefix:发篇关于控制台的逆向水一下,没啥技术含量,可能内容火星了,大牛飘过即可~ prefix2:求正式会员身份…… 每个学C语言的人都不会不知道printf()这个函数,第一个程序经常...

C标准库学习之<stdarg.h> ——不定参数处理

#include #include #include // chu理可bian参数 #include/* =============================================...

标准 C 库 <stdarg.h>

va_list是一个宏,由va_start和va_end界定   2011-05-25 22:14:23|  分类: C++ |  标签: |字号大中小 订阅 在C/C++函数中使用可变参数...

《C标准库》——之<stdarg.h>

C语言变长参数
  • JY_95
  • JY_95
  • 2015年05月10日 10:05
  • 630

C可变长参数表问题---stdarg.h宏定义va_start,va_arg,va_end

一、什么是可变参数 我们在C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为:  int printf( const char* format, ...);  ...
  • weio147
  • weio147
  • 2016年11月28日 10:12
  • 98

C标准库参考指南系列译文(10)stdarg.h

英文原文:http://www.acm.uiuc.edu/webmonkeys/book/c_guide/2.10.html 原文作者:Eric Huss 中文译者:柳惊鸿 Poechant 版...

变参函数——stdarg——printf——variable and function

va_list是一个宏,由va_start和va_end界定。     typedef char* va_list;   void va_start ( va_list ap, prev_param ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:探秘C<stdarg.h>实现自己的printf( )
举报原因:
原因补充:

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