深入printf /*** *printf.c - print formatted * * Copyright (c) 1985-1997, Microsoft Corporation. All rights reserved. * *Purpose: * defines printf() - print formatted data * *******************************************************************************/ #include #include #include #include #include #include #include /*** *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; va_start(arglist, format); _ASSERTE(format != NULL);//断言宏。如果输出格式字符串指针为空,则在DEBUG版下断言,报告错误。 _lock_str2(1, stdout); buffing = _stbuf(stdout);//stdout:指定输出到屏幕 retval = _output(stdout,format,arglist); _ftbuf(buffing, stdout); _unlock_str2(1, stdout); return(retval); } 以上为printf()的源代码 1、从含有可选参数函数中获得可选参数,以及操作这些参数 typedef char *va_list; void va_start( va_list arg_ptr, prev_param ); type va_arg( va_list arg_ptr, type ); void va_end( va_list arg_ptr ); 假定函数含有一个必选参数和多个可选参数,必选参数声明为普通数据类型,且能通过参数名来获得该变量的值。可选参数通过宏va_start、va_arg和va_end(定义在stdarg.h或varargs.h中)来进行操作,即通过设置指向第一个可选参数指针、返回当前参数、在返回参数后重新设置指针来操作所有的可选参数。 va_start:为获取可变数目参数的函数的参数提供一种便捷手段。设置arg_ptr为指向传给函数参数列表中的第一个可选参数的指针,且该参数必须是va_list类型。prev_param是在参数列表中第一个可选参数前的必选参数。 va_arg:返回由arg_ptr所指向的参数的值,且自增指向下一个参数的地址。type为当前参数的类型,用来计算该参数的长度,确定下一个参数的起始位置。它可以在函数中应用多次,直到得到函数的所有参数为止,但必须在宏va_start后面调用。 va_end:在获取所有的参数后,设置指针arg_ptr为NULL。 下面举例说明: #include #include int average( int first, ... ); void main( void ) { /* Call with 3 integers (-1 is used as terminator). */ printf( "Average is: %d\n", average( 2, 3, 4, -1 ) ); /* Call with 4 integers. */ printf( "Average is: %d\n", average( 5, 7, 9, 11, -1 ) ); /* Call with just -1 terminator. */ printf( "Average is: %d\n", average( -1 ) ); } int average( int first, ... ) { int count = 0, sum = 0, i = first; va_list marker; va_start( marker, first ); /* Initialize variable arguments. */ while( i != -1 ) { sum += i; count++; i = va_arg( marker, int); } va_end( marker ); /* Reset variable arguments. */ return( sum ? (sum / count) : 0 ); } 返回值为: Average is: 3 Average is: 8 Average is: 0 综上所述,在printf()函数中,可以只输出一个字符串,也可按照一定的形式输出含有多个可选参数的字符串信息。因此,首先就要通过这些宏来获取所有的可选参数。在上面的源码可以看出printf()中,只使用了宏at_start,将可选参数的首地址赋给了arglist。 2、锁定字符串及输出字符串到屏幕 #define _lock_str2(i,s) _lock_file2(i,s) void __cdecl _lock_file2(int, void *); #define _unlock_str2(i,s) _unlock_file2(i,s) void __cdecl _unlock_file2(int, void *); int __cdecl _stbuf(FILE *); void __cdecl _ftbuf(int, FILE *); int __cdecl _output(FILE *, const char *, va_list); 在output函数中,读取格式字符串中的每一个字符,然后对其进行处理,处理方式根据每一个字符所代表的意义来进行,如:普通字符直接利用函数WRITE_CHAR(ch, &charsout);输出到控制台。 其中的主要部分是对转换说明符(d,c,s,f)的处理,现在将对其中的部分代码进行详细说明,这里只说明最基本的转换说明符,对这些须基本的转换说明符进行修饰的修饰符,程序中单独进行处理。下面是函数output()(output.c)部分源代码: case ST_TYPE: //表示当前处理的字符的类型为转换说明符。 ... switch (ch) { //下面对参数的获取都是利用宏va_arg( va_list arg_ptr, type );来进行的。 case ''c'': { //从参数表中获取单个字符,输出到缓冲字符串中,此时,type=int buffer[0] = (char) get_int_arg(&argptr); /* get char to print */ text = buffer; textlen = 1; /* print just a single character */ } break; case ''s'': { //从参数表中获取字符串,输出到缓冲字符串中,此时,type=char* int i; char *p; /* temps */ text = get_ptr_arg(&argptr); ... } break; case ''w'': { //对宽字符进行处理 ... } /* case ''w'' */ break; ... case ''e'': case ''f'': case ''g'': { //对浮点数进行操作 ... #if !LONGDOUBLE_IS_DOUBLE /* do the conversion */ if (flags & FL_LONGDOUBLE) { _cldcvt((LONGDOUBLE*)argptr, text, ch, precision, capexp); va_arg(argptr, LONGDOUBLE); //对长双精度型进行处理,此时,type=long double } else #endif /* !LONGDOUBLE_IS_DOUBLE */ { //对双精度型进行处理,此时,type=double _cfltcvt((DOUBLE*)argptr, text, ch, precision, capexp); va_arg(argptr, DOUBLE); } ... break; //对整型变量处理 case ''d'': case ''i'': ... goto COMMON_INT; case ''u'': radix = 10; goto COMMON_INT; case ''p'': ... goto COMMON_INT; case ''o'': ... 注:对于浮点型double和long double,有相应的转换说明符(%f表示双精度型,%lf表示长双精度型),而float却没有。其中的原因是,在K&RC下,float值用于表达式或用作参数前,会自动转换成double类型。而ANSI C一般不会自动把float转换成double。有些程序已假定其中的float参数会被转换成double,为了保护大量这样的程序,所有printf()函数的float参数还是被自动转换成double型。因此,在K&RC或ANSI C下,都无需用特定的转换说明符来显示float型。 综上所述,转换说明符必须与待打印字符的类型。通常,用户有种选择。例如,如要打印一个int类型的值。则只可以使用%d,%x或%o。所有这些说明符都表示要打印一个int类型的值;它们只不过提供了一个数值的几种不同表示。类似一,可以用%f、%g和%e来表示double类型的值。但如果转换说明与类型不匹配,将会出现意想不到的结果。为什么呢?问题就在于C向函数传递信息的方式。 这个失败的根本细节与具体实现相关。它决定了系统中的参数以何方式传递。函数调用如下: float n1; double n2; long n3; long n4; ... printf("%ld,%ld,%ld,%ld",n1,n2,n3,n4); 这个调用告诉计算机,要把变量n1,n2,n3和n4的值交给计算机,它把这些变量放进称作栈(stack)的内存区域中,来完成这一任务。计算机把这些值放进栈中,其根据是变量的类型而不是转换说明符,比如n1,把8个字节放入栈中(float被转换成double),类似地,为n2放了8字节,其后给n3和n4各放了4个字节。接着,控制的对象转移到printf();此函数从栈中读数,不过在这一过程中,它是在转换说明符的指导下,读取数值的。说明符%ld指定printf()应读4个字节(va_arg( va_list arg_ptr, type )中type=long),因此printf()读入栈中的4个字节,作为它的第一个值。但是这只是n1的前半部分,这个值被看成一个long整数。下一个说明符%ld读入4个字节,这正是n1的后半部分,这个值被看成第二个long整数。类似地,第三、第四次又读入n2的前后两部分。因此,尽管我们对n3和n4使用了正确的说明符,printf()仍然会产生错误。
用户1131489 2016-3-29 15:01
用户377235 2015-11-3 17:09
用户377235 2013-9-14 17:20
用户377235 2013-3-25 19:56
用户430875 2013-3-11 17:24
用户377235 2013-3-8 12:42
用户1626492 2013-2-23 10:00
用户377235 2013-2-17 19:42
用户377235 2012-12-28 07:59
用户1062428 2012-10-16 13:51