printf函数的实现方式

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了printf函数的实现方式相关的知识,希望对你有一定的参考价值。

我在C primer plus中,在讲到printf函数时用了这样一个例子
printf(“%ld%ld%ld%ld”,x1,x2,x3,x3)x1,x2是双精度数据,x3,x4是长整型数据
输出时,x1,x2肯定输出错误值,然后x3,x4也是输出的错误值,书中是这样解释的
把x1x2x3x4按其本身的数据类型放入堆札中,x1(8字节)x2(8字节)x3(4字节)x4(4字节)
那么printf函数在堆札中读取时,按格式说明符的长度(4字节)来读取,这样由于错位造成了x3,x4也连带受到影响
我的问题是,书中的例子是传入堆札中的数据总长度比格式说明符的总长度大。如果反过来
传入堆札中的数据总长度比格式说明符的总长度小,怎么办呢??比如:
short int x=1,y=1;
printf("%d\n%d\n",x,y);
按照书中的理论,x,y各自以2个字节传入堆札,一共是4个字节,那么printf函数第一个%d以4个字节刚好读取堆札中的所有数据,然后第二个%d就没有数据可读了,预想的输出肯定是x,y都是错误的输出
但是实际测试以后发现能正常输出2个1。
这是为什么呢??求高手解答!

其实printf和getchar()类似,它们都是一个”外壳“,真正实现功能的不是它本身,而是通过调用别的函数。
getchar() is equivalent to getc(stdin).
printf有一家子print函数

printf, fprintf, sprintf, snprintf, vprintf, vfprintf, vsprintf, vsnprintf - formatted output conversion
它们的声明在不同的header file里面

#include <stdio.h>

int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);
int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);

#include <stdarg.h>

int vprintf(const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
snprintf(), vsnprintf():
这两个函数是C99新加的,编译的时候 注意 -std=c99

实现之前还是“复习”一下printf比较好,就当是铺垫
有意思的是printf的declaration。
int printf(const char *format, ...);
返回值是int,第一个参数是const字符串指针,第二个参数是个...
先看看返回值int有哪些情况
Return value
Upon successful return, these functions return the number of characters printed (excluding the null byte used to end output to strings).

嗯哼。。。返回的是成功打印的字符的个数,这里不包括NULL
demo:
#include<stdio.h>

int main()

int counter = 0;

counter = printf("hello world! %d\n",10);

printf("counter is %d\n",counter);

return 0;

jasonleaster@ubuntu:~$ ./a.out
hello world! 10
counter is 16

接着,第一个参数是一个指针,指向const字符串
Format of the format string
The format string is a character string, beginning and ending in its initial shift state, if any. The format string is composed of
zero or more directives: ordinary characters (not %), which are copied unchanged to the output stream; and conversion specifications,
each of which results in fetching zero or more subsequent arguments. Each conversion specification is introduced by the character %,
and ends with a conversion specifier. In between there may be (in this order) zero or more flags, an optional minimum field width,
an optional precision and an optional length modifier.

很少人会用下面这种用法
printf("%*d",10,5);
我第一次遇到的时候,可以说是“惊愕”,究竟会打印什么东西。折腾了好久,最后搞定了。总结在这里
http://blog.csdn.net/cinmyheart/article/details/10116359

Format of the format string

The format string is a character string, beginning and ending in its initial shift state, if any. The format string is composed of zero or more directives: ordinary characters (not %), which are copied unchanged to the output stream; and conversion specifications, each of which results in fetching zero or more subsequent arguments. Each conversion specification is introduced by the character %, and ends with a conversion specifier. In between there may be (in this order) zero or more flags, an optional minimum field width, an optional precision and an optional length modifier.
The arguments must correspond properly (after type promotion) with the conversion specifier. By default, the arguments are used in the order given, where each '*' and each conversion specifier asks for the next argument (and it is an error if insufficiently many arguments are given). One can also specify explicitly which argument is taken, at each place where an argument is required, by writing "%m$" instead of '%' and "*m$" instead of '*', where the decimal integer m denotes the position in the argument list of the
desired argument, indexed starting from 1. Thus,
printf("%*d", width, num);
and
printf("%2$*1$d", width, num);
are equivalent. The second style allows repeated references to the same argument. The C99 standard does not include the style using '$', which comes from the Single UNIX Specification. If the style using '$' is used, it must be used throughout for all conversions taking an argument and all width and precision arguments, but it may be mixed with "%%" formats which do not consume an argument.
There may be no gaps in the numbers of arguments specified using '$'; for example, if arguments 1 and 3 are specified, argument 2
must also be specified somewhere in the format string.

第三个参数 ...
嗯,这家伙有点屌,叫做变长参数。把这个搞定,C总会有点长进的
这个stdarg.h 我在现在的GCC和现在的linux 3.0版本的内核里面找了好久,都木有,估计是封装到被的地方了。。。。
__builtin_va_start(v,l) 线索就死在这个地方。。。之后就找不到__builtin_va_start的定义了

还是看早起内核的实现吧
0.12内核里面的stdarg.h
#ifndef _STDARG_H
#define _STDARG_H

typedef char *va_list;

/* Amount of space required in an argument list for an arg of type TYPE.
TYPE may alternatively be an expression whose type is used. */

#define __va_rounded_size(TYPE) \
(((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))

#ifndef __sparc__
#define va_start(AP, LASTARG) \
(AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#else
#define va_start(AP, LASTARG) \
(__builtin_saveregs (), \
AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#endif

void va_end (va_list); /* Defined in gnulib */
#define va_end(AP)

#define va_arg(AP, TYPE) \
(AP += __va_rounded_size (TYPE), \
*((TYPE *) (AP - __va_rounded_size (TYPE))))

#endif /* _STDARG_H */

va_list 是一个指向字符串的指针
分析上面的宏定义#define __va_rounded_size(TYPE) \
(((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))
这个用来得到TYPE元素类型的字节大小,若不足4字节(例如short 和char),那么认为这个元素的大小为4字节,简单的说就是检测元素的大小,不足4字节的当作4字节看待。。。#define va_start(AP, LASTARG) \
(AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))

AP一般都是va_list,LASTARG则是 指向参数变长函数的格式化字符串的指针.
va_start的作用就很明显了。取得变长参数列表的第一个参数的地址。
va_end 则是把指针va_list 置0 (通过google知道的,这个va_end真没找到定义,代码里面就一句#define 我无能为力啊。。。)
不过知道用va_start 和va_end 就OK啦
下面先来个变长参数的demo
/*****************************************************************************
code writer : EOF
code date : 2014.04.26
e-mail:jasonleaster@gmail.com
code purpose:
just a demo for varible parameter function.

usage: va_sum(a number,anohter number...,0);
va_sum(1,2,3,4,5,0); return 15

******************************************************************************/

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

int va_sum(int* a,...);

int main()

int number = 1;

int foo = 0;

foo = va_sum(&number,2,3,4,5,0);

return 0;


int va_sum(int* a,...)

int counter = 0;
int element = 0;

va_list arg;

va_start(arg,a);

while((element = va_arg(arg,int)) != 0)

counter += element;


va_end(arg);

return counter;


#define va_arg(AP, TYPE) \
(AP += __va_rounded_size (TYPE), \
*((TYPE *) (AP - __va_rounded_size (TYPE))))
参考技术A 因为,由短字节的数据向长字节的数据进行转换时,系统会自动在高位补0,如果由长字节的向短字节的数据进行转换高位就会丢失. 参考技术B

不是高手,下面有C的printf源码,希望对你有帮助,其实我是看不懂的,没有一定的基础这种源码是很难懂得。对你“如果反过来传入堆札中的数据总长度比格式说明符的总长度小,怎么办呢??”的疑问,用的是传入short类型的参数,格式符用的是%d,结果正确。个人认为printf在实现的时候对这些问题是有处理的,因为%d格式符输出的是十进制整数,%f输出的是浮点数,但是十进制整数有short, int, long三种类型,浮点数有float, double两种,所以%d能够解析short, int, long三种类型,%f能够解析float, double两种类型。书上的例子之所以错了,是因为一开始就用%d去解释double类型的数据,所以从一开始就错了,导致后面的数据就错了。在 printf 传参数的时候,只要不交叉格式符和数据类型(即用%d解释浮点型的,用%f解析十进制整数型的),应该都是不会出问题的。至于说用各种数据类型对应的字节数去解释上面的例子,这个应该这样解释:printf 函数在在遇到一个格式符时,会根据后面对应参数的实际类型去读取固定长度的数据,而不是说遇到%d就读四字节。上面纯属个人看法,仅供参考,大家共同学习。你可以用其他的数据类型多测试几次。

/***
*printf.c - print formatted
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
*       defines printf() - print formatted data
*
*******************************************************************************/

#include <cruntime.h>
#include <stdio.h>
#include <dbgint.h>
#include <stdarg.h>
#include <file2.h>
#include <internal.h>
#include <mtdll.h>
#include <stddef.h>
#include <process.h>

/***
*int printf(format, ...) - print formatted data
*
*Purpose:
*       Prints formatted data on stdout using the format string to
*       format data and getting as many arguments as called for
*       Uses temporary buffering to improve efficiency.
*       _output does the real work here
*
*Entry:
*       char *format - format string to control data format/number of arguments
*       followed by list of arguments, number and type controlled by
*       format string
*
*Exit:
*       returns number of characters printed
*
*Exceptions:
*
*******************************************************************************/

int __cdecl printf (
        const char *format,
        ...
        )
/*
 * stdout 'PRINT', 'F'ormatted
 */

    va_list arglist;
    int buffing;
    int retval;

    _VALIDATE_RETURN( (format != NULL), EINVAL, -1);

    va_start(arglist, format);

    _lock_str2(1, stdout);
    __try 
        buffing = _stbuf(stdout);

        retval = _output_l(stdout,format,NULL,arglist);

        _ftbuf(buffing, stdout);

    
    __finally 
        _unlock_str2(1, stdout);
    

    return(retval);


int __cdecl _printf_l (
        const char *format,
        _locale_t plocinfo,
        ...
        )

    va_list arglist;

    va_start(arglist, plocinfo);

    return _vprintf_l(format, plocinfo, arglist);



int __cdecl _printf_s_l (
        const char *format,
        _locale_t plocinfo,
        ...
        )

    va_list arglist;

    va_start(arglist, plocinfo);

    return _vprintf_s_l(format, plocinfo, arglist);


int __cdecl printf_s (
        const char *format,
        ...
        )

    va_list arglist;

    va_start(arglist, format);

    return _vprintf_s_l(format, NULL, arglist);


int __cdecl _printf_p_l (
        const char *format,
        _locale_t plocinfo,
        ...
        )

    va_list arglist;

    va_start(arglist, plocinfo);

    return _vprintf_p_l(format, plocinfo, arglist);


int __cdecl _printf_p (
        const char *format,
        ...
        )

    va_list arglist;

    va_start(arglist, format);

    return _vprintf_p_l(format, NULL, arglist);


static UINT_PTR __enable_percent_n = 0;

/***
*int _set_printf_count_output(int)
*
*Purpose:
*   Enables or disables %n format specifier for printf family functions
*
*Internals:
*   __enable_percent_n is set to (__security_cookie|1) for security reasons;
*   if set to a static value, an attacker could first modify __enable_percent_n
*   and then provide a malicious %n specifier.  The cookie is ORed with 1
*   because a zero cookie is a possibility.
******************************************************************************/
int __cdecl _set_printf_count_output(int value)

    int old = (__enable_percent_n == (__security_cookie | 1));
    __enable_percent_n = (value ? (__security_cookie | 1) : 0);
    return old;


/***
*int _get_printf_count_output()
*
*Purpose:
*   Checks whether %n format specifier for printf family functions is enabled
******************************************************************************/
int __cdecl _get_printf_count_output()

    return ( __enable_percent_n == (__security_cookie | 1));

本回答被提问者和网友采纳

模拟实现printf函数

模拟实现 printf()函数


如果要想解决这个问题 ,就要 知道一个知识点 ,可变参数列表  , 可变参数列表是通过宏来实现的,这些宏定义于stdarg.h头文件中,它是标准库的一部分。这个头文件声明
个类型va_list 和三个宏va_start、va_arg和va_end。
转到定义处 查看一下这几个宏和类型 
typedef char *  va_list;

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

#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

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

#define _crt_va_end(ap)      ( ap = (va_list)0 )
从上 面可以看出  va_list 表示的是 一个 char*的指针 
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
_INSIZEOF( n) 表示 的 是将 一个类型 如果该类型的空间字节数为"1 2 3 4"  都将它进位 为4字节                                                                如果该类型的空间字节数为"5 6 7 8 " 都将它进位 为8字节 
#define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
表示的是 将 ap跳过第一个参数 v ,指向下一个 
#define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
表示的是 要计算的参数,但是此时的 ap指向的是下一个参数,这样就可以为下一个参数的使用做好基础
#define _crt_va_end(ap)      ( ap = (va_list)0 )
结束 后 将 ap  赋值为 \\0; 下面是我写的一个简单 的printf()函数  代码实现
#define  _CRT_SECURE_NO_WARENINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<stdarg.h>
void  Puts(const char *src)//我自己定义的puts 函数 输出时不会输出‘\\n’

	while(*src)
	
		putchar(*src);
		src++;
	

void My_printf(const char *src,...)

	va_list arg;
	va_start(arg,src);
	while(*src)//判断 字符串是否 结束 
	
		if(*src =='s')
		 
			Puts(va_arg(arg,char*));//在这块不能使用puts()函数 ,因为会多输出一个 '\\n'
		
		else if(*src == 'c')
		
			 putchar(va_arg(arg,char));
		
		else   //如果不是类型名 就 原型输出
			putchar(*src);
		src++;
	
	va_end(arg);

int main()

	My_printf("s\\n ccc.\\n","hello",'b','i','t');
	system("pause");
	return 0 ;



以上是关于printf函数的实现方式的主要内容,如果未能解决你的问题,请参考以下文章

printf 函数的实现原理

STM32串口打印的那些知识

STM32串口打印的那些知识

python中的printf:%号拼接字符串和format函数

实现类似printf这样的函数

s3c2440——实现裸机的简易printf函数