探秘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...
  • zhangyulin54321
  • zhangyulin54321
  • 2012年08月24日 16:30
  • 2109

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

va_list是一个宏,由va_start和va_end界定。     typedef char* va_list;   void va_start ( va_list ap, prev_param ...
  • u011349664
  • u011349664
  • 2013年10月05日 15:31
  • 921

自己实现一个printf函数

在ARM嵌入式开发环境中,串口一般使用ARM PL011的uart实现,uart的实现原理就是实现了一个8bits宽度,32深度的fifo,不停的往屏幕输出一个byte,一个byte。这个就是硬件的实...
  • wuchikuangtu
  • wuchikuangtu
  • 2016年10月23日 11:06
  • 331

c语言—stdarg宏(函数可变参数实现)

可变参数实现
  • april2009128
  • april2009128
  • 2015年12月10日 21:46
  • 612

模拟实现printf函数

在学习C语言的时候,使用最多的就是printf函数了,常常用,却没有认真的思考过printf函数是如何实现的。 查了查msdc后,有了点心得,连忙记录下来。 呈上msdn上printf函数的主要相关信...
  • qq_35524916
  • qq_35524916
  • 2016年11月10日 09:34
  • 672

用C和C++,两种方式实现C语言中的printf函数

理论准备 C++中的类C可变形参函数 首先,我们来看一下C++中的类C可变形参函数。可变形参函数,表示函数形参的类型和个数都可以变化。C语言中的 printf, scanf 就是最常见的可变形参函数。...
  • friendbkf
  • friendbkf
  • 2015年05月20日 21:36
  • 1763

可变参数的使用-printf简单实现

我们在写一个程序的时候,经常用到一些函数,例如printf函数,在我们用的时候觉得并没有什么觉得他很简单啊,我们使用的时候都没有注意过,它其实有很多种调用方法。 例如: 其实这就是可变...
  • zhouchaoya142526
  • zhouchaoya142526
  • 2017年04月23日 11:20
  • 331

[C语言]头文件之stdarg

一、stdrag简介      #include standard arguments     stdarg.h是C语言中C标准函数库的头文件,stdarg是由standard(标准)和 argu...
  • SMUEvian
  • SMUEvian
  • 2017年03月19日 16:52
  • 262

根据printf函数来讲解可变参数

在C语言中常用printf函数输出各种类型的数值,而且输出的参数可多可少,这就涉及到了可变参数函数的定义。     在此模拟实现printf函数,可完成下面的功能,能完成下面函数的调用。 print...
  • wuxinrenping
  • wuxinrenping
  • 2017年11月08日 22:37
  • 43

stdarg宏的用法

#include #include int sum(int n_values, ...) { va_list var_arg; int count; int sum = 0; va_s...
  • atinybirdinit
  • atinybirdinit
  • 2015年01月05日 14:07
  • 179
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:探秘C<stdarg.h>实现自己的printf( )
举报原因:
原因补充:

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