printf 函数使用 & 可变参数函数实现原理

printf 函数使用 & 可变参数函数实现原理,第1张

一. Printf 和scanf 函数格式

Printf 和 scanf 家族函数都属于可变参数函数(variadic function)。这种函数需要固定数量的强制参数,后面是数量可变的可选参数。
这种函数必须至少有一个强制参数。可选参数的类型可以变化。可选参数的数量由强制参数的值决定,或由用来定义可选参数列表的特殊值决定。

1. Scanf

scanf家族的原型如下。

int  fscanf(FILE  *stream,char  const *format,);    
int   scanf(char const  *format,); 
int  sscanf(char  const  *format,);

每个原型中的省略号表示一个可变长度的指针列表。以上这些函数都从输入源读取字符,并根据format字符串给出的格式代码对它们进行转换。

  • fscanf的输入源就是作为参数给出的流

  • scanf从标准输入读取

  • sscanf从第一个参数所给出的字符串中读取字符。

  • 当格式化字符串到达末尾或者读取的输入不再匹配格式字符串所指定的类型时,输入就停止。

  • 在任何一种情况下,被转换的输入值的数目作为函数的返回值返回。

  • 如果在任何输入值被转换之前文件就已到达尾部,函数就返回常量值EOF。

Sample code:

    int main(void)
    {
        int a;
        float b;
        int ret;
 
        printf("请输入a和b:");
        ret = scanf("%d%f",&a,&b);
        printf("ret = %d\n",ret);
        printf("a = %d,b = %.2f\n",a,b);
 
        return 0;
    }
2 . printf

printf函数家族用于创建格式化的输出。这个家族共有三个函数:fprintf、printf 和sprintf。它们的原型如下:

int  fprintf(FILE  *stream,char const  *format,);

int printf(char  const  *format,);

int  sprint(char  *buffer,char const  *format,);
  • printf根据格式代码和format参数中的其他字符对参数列表中的值进行格式。
  • 使用printf,将输出结果输出到标准输出。
  • 使用fprintf,可以使用任何输出流。
  • sprintf把它的结果作为一个NUL结尾的字符串存储到指定的buffer缓冲区而不是写人到流中。
  • 这三个函数的返回值是实际打印或存储的字符数。
二. 可变参数函数实现 1. 原理

由于在函数调用时,参数是按顺序传递入栈(从右到左,左低右高)。所以当知道第一个参数的地址后,就可以找到其他参数的地址。

注:

  • 必须知道第一个参数,也就是最后 一个入栈的参数地址,不能全是可变参数。
  • 必须知道其余参数的个数和类型,否则无法准确找到其余参数的地址。prinf函数中的格式化字符串就包含了可变参数的个数和类型信息。
2. 具体实现

头文件:stdarg.h 里定义了几个宏,用于实现可变参数传递。

功能参数说明
va_list可变参数类型实际上是一个char* 指针
_INTSIZEOF(v)取变量在栈中的长度传入变量名
va_start(va_list ap,first_para)初始化va_list传入va_list对象和起始参数
va_arg(va_list ap, type)获取下一个可变参数va_list对象和下一个可变参数的类型
va_end(va_list)清理va_list传入va_list对象
va_list

typedef char * va_list; 该指针用于指向当前正在处理的参数。

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

这个宏的目的是将变量的地址4字节对齐(当int是4字节时)。“&~(sizeof(int)-1)”恰好可以将变量最后两位清零,即实现四字节对齐。
那为什么要对齐呢?
我的理解是,这和系统架构有关,对齐实际是将压入堆栈的参数长度格式化,这样便于管理堆栈和参数。

va_start
#define va_start(ap, v) ((void)(ap = (va_list)&v + _INTSIZEOF(v))))

这个宏的目的就是根据传入参数的第一个参数,获取可变参数列表的第一个参数的地址。

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

这个宏有两个作用。第一是取出当前参数的值,传递给调用者。第二是更新ap指向下一个参数。
Usage:

#define GET_IARG(flag, ap) \
	(flag&LONGLONG ? (long long)va_arg(ap, long long):\
	(flag&LONGINT ? va_arg(ap, long):\
	flag&SHORTSHORT ? (signed char)va_arg(ap, int):\ // char 类型的缺省值可能是signed或unsigned类型的,保险起见前面要加signed.
	flag&SHORTINT ? (short)va_arg(ap, int):\
	flag&SIZET?     (ssize_t)va_arg(ap, ssize_t):\
	flag&INTMAXT?     (intmax_t)va_arg(ap, intmax_t):\
	va_arg(ap, int)))

#define GET_UARG(flag, ap) \
	(flag&LONGLONG ? (unsigned long long)va_arg(ap, unsigned long long):\
	(flag&LONGINT ? va_arg(ap, unsigned long):\
	flag&SHORTSHORT ? (unsigned char)va_arg(ap, unsigned int):\
	flag&SHORTINT ? (unsigned short)va_arg(ap, unsigned int):\
	flag&SIZET?     (size_t)va_arg(ap, size_t):\
	flag&INTMAXT?     (uintmax_t)va_arg(ap, uintmax_t):\
	va_arg(ap, unsigned int)))
va_end
#define va_end(ap) (ap=(va_list)0)

将指针ap清零。

3. use case
void myPtintf(char *format, ...)
{
    va_list args;

    va_start(args, format);
    vprintf(format, args);//用户自定义的printf函数具体参数解析,其中va_arg的使用方式参考GET_IARG/GET_UARG宏
    va_end(args);//va_start 和va_stop必须成对使用
}
三.嵌入式系统中printf函数的具体实现

//TODO: 涉及uart_putc() 、fputc()等uart协议底层驱动函数的实现。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/2991684.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-23
下一篇 2022-09-23

发表评论

登录后才能评论

评论列表(0条)

保存