深入剖析printf函数(下):---形参列表和格式化输出是如何做到的?
来源:互联网 发布:客服淘宝退换货流程图 编辑:程序博客网 时间:2024/05/22 04:59
深入剖析printf函数(下):
---形参列表和格式化输出是如何实现的?
(作者:LL 出处:http://blog.csdn.net/tcpipstack , 欢迎转载,也请保留这段声明。谢谢!)
一、引言
在上一篇 [Linux内核小白]深入剖析printf函数(上):如何不借助第三方库在屏幕上输出"Hello World"? 里,我们已经实现了用汇编语言在屏幕上输出了“Hello World”, 迈出了万里长征的第一步,但是我们知道实际的printf的功能是十分强大的,它和scanf一样属于标准输入输出的一种格式化函数,我们一般是这样使用它的:
printf()的基本形式:printf("格式控制字符串",变量列表);
二、格式化输出
printf()函数是格式输出函数,请求printf()打印变量的指令取决与变量的类型.
例如,在打印整数是使用%d符号,在打印字符是用%c 符号.这些符号被称为转换说明.因为它们指定了如何不数据转换成可显示的形式.
下列列出的是ANSI C标准printf()提供的各种转换说明.
转换说明及作为结果的打印输出
%a 浮点数、十六进制数字和p-记数法(C99)
%A 浮点数、十六进制数字和p-记法(C99)
%c 一个字符
%d 有符号十进制整数
%e 浮点数、e-记数法
%E 浮点数、E-记数法
%f 浮点数、十进制记数法
%g 根据数值不同自动选择%f或%e.
%G 根据数值不同自动选择%f或%e.
%i 有符号十进制数(与%d相同)
%o 无符号八进制整数
%p 指针
%s 字符串
%u 无符号十进制整数
%x 使用十六进制数字0f的无符号十六进制整数
%X 使用十六进制数字0f的无符号十六进制整数
%% 打印一个百分号
三、形参列表的读入
printf函数的参数列表是如下的形式:
int printf(const char *fmt, ...)
类似于上面参数列表中的token:...,介个是可变形参的一种写法。当传递参数的个数不确定时,就可以用这种方式来表示。
但是电脑比程序员更笨,函数体必须知道具体调用时参数的个数才能保证顺利执行,那么我们必须寻找一种方法来了解参数的个数。
让我们先回到代码中来:
- /************************************************************************************
- ** File: - Z:\code\c\LLprintf\print2.1\LLprintf.c
- **
- ** Copyright (C), Long.Luo, All Rights Reserved!
- **
- ** Description:
- ** LLprintf.c
- **
- ** Version: 2.0
- ** Date created: 23:56:33,24/01/2013
- ** Author: Long.Luo
- **
- ** --------------------------- Revision History: --------------------------------
- ** <author> <data> <desc>
- **
- ************************************************************************************/
- #include "LLprintf.h"
- // Lprintf
- int Lprintf(const char *fmt, ...)
- {
- int i;
- char buf[256];
- va_list arg = (va_list)((char*)(&fmt) + 4); /*4是参数fmt所占堆栈中的大小*/
- i = vsLprintf(buf, fmt, arg);
- buf[i] = 0;
- LLprint(buf, i);
- return i;
- }
如上面代码中的:
va_list arg = (va_list)((char*)(&fmt) + 4);
而va_list的定义:
typedef char *va_list
这说明它是一个字符指针。
其中的: (char*)(&fmt) + 4) 表示的是...中的第一个参数。
大家肯定很迷惑,不急,再详细解释:
C语言中,参数压栈的方向是从右往左。也就是说,当调用printf函数的适合,先是最右边的参数入栈。
fmt是一个指针,这个指针指向第一个const参数(const char *fmt)中的第一个元素。
fmt也是个变量,它的位置,是在栈上分配的,它也有地址。
对于一个char *类型的变量,它入栈的是指针,而不是这个char *型变量。
换句话说:
你sizeof(p) (p是一个指针,假设p=&i,i为任何类型的变量都可以)
得到的都是一个固定的值。(我的计算机中都是得到的4)
当然,我还要补充的一点是:栈是从高地址向低地址方向增长的。
现在我想你该明白了:为什么说(char*)(&fmt) + 4) 表示的是...中的第一个参数的地址。
为毛我还是不明白啊????
不急,我给你更直观的解释:
- /******************************************************************************************
- 可变参数函数调用原理(其中涉及的数字皆为举例)
- ===========================================================================================
- i = 0x23;
- j = 0x78;
- char fmt[] = "%x%d";
- printf(fmt, i, j);
- push j
- push i
- push fmt
- call printf
- add esp, 3 * 4
- ┃ HIGH ┃ ┃ HIGH ┃
- ┃ ... ┃ ┃ ... ┃
- ┣━━━━━━━━━━┫ ┣━━━━━━━━━━┫
- ┃ ┃ 0x32010┃ '\0' ┃
- ┣━━━━━━━━━━┫ ┣━━━━━━━━━━┫
- 0x3046C┃ 0x78 ┃ 0x3200c┃ d ┃
- ┣━━━━━━━━━━┫ ┣━━━━━━━━━━┫
- arg = 0x30468┃ 0x23 ┃ 0x32008┃ % ┃
- ┣━━━━━━━━━━┫ ┣━━━━━━━━━━┫
- 0x30464┃ 0x32000 ───╂────┐ 0x32004┃ x ┃
- ┣━━━━━━━━━━┫ │ ┣━━━━━━━━━━┫
- ┃ ┃ └──→ 0x32000┃ % ┃
- ┣━━━━━━━━━━┫ ┣━━━━━━━━━━┫
- ┃ ... ┃ ┃ ... ┃
- ┃ LOW ┃ ┃ LOW ┃
- 实际上,调用 vsprintf 的情形是这样的:
- vsLprintf(buf, 0x32000, 0x30468);
- ******************************************************************************************/
下面我们来看看下一句:
i = vsLprintf(buf, fmt, arg);
这句起什么作用呢?
让我们进入下一节:对参数进行格式化处理。
四、参数格式化输出
让我们来看看vsLprintf(buf, fmt, arg)是什么函数:- int vsLprintf(char *buf, const char *fmt, va_list args)
- {
- char *p;
- int m;
- char inner_buf[STR_DEFAULT_LEN];
- char cs;
- int align_nr;
- va_list p_next_arg = args;
- for (p=buf; *fmt; fmt++) {
- if (*fmt != '%') {
- *p++ = *fmt;
- continue;
- }
- else { /* a format string begins */
- align_nr = 0;
- }
- fmt++;
- if (*fmt == '%') {
- *p++ = *fmt;
- continue;
- }
- else if (*fmt == '0') {
- cs = '0';
- fmt++;
- }
- else {
- cs = ' ';
- }
- while (((unsigned char)(*fmt) >= '0') && ((unsigned char)(*fmt) <= '9')) {
- align_nr *= 10;
- align_nr += *fmt - '0';
- fmt++;
- }
- char * q = inner_buf;
- memset(q, 0, sizeof(inner_buf));
- switch (*fmt) {
- case 'c':
- *q++ = *((char*)p_next_arg);
- p_next_arg += 4;
- break;
- case 'x':
- m = *((int*)p_next_arg);
- i2a(m, 16, &q);
- p_next_arg += 4;
- break;
- case 'd':
- m = *((int*)p_next_arg);
- if (m < 0) {
- m = m * (-1);
- *q++ = '-';
- }
- i2a(m, 10, &q);
- p_next_arg += 4;
- break;
- case 's':
- strcpy(q, (*((char**)p_next_arg)));
- q += strlen(*((char**)p_next_arg));
- p_next_arg += 4;
- break;
- default:
- break;
- }
- int k;
- for (k = 0; k < ((align_nr > strlen(inner_buf)) ? (align_nr - strlen(inner_buf)) : 0); k++) {
- *p++ = cs;
- }
- q = inner_buf;
- while (*q) {
- *p++ = *q++;
- }
- }
- *p = 0;
- return (p - buf);
- }
这个函数起什么作用呢?
我们回想下printf起什么作用呢?
哦,printf接受一个格式化的命令,并把指定的匹配的参数格式化输出。
好的,我们再看看i = vsLprintf(buf, fmt, arg);
vsLprintf返回的是一个长度值,那这个值是什么呢?
会不会是打印出来的字符串的长度呢?
没错,返回的就是要打印出来的字符串的长度。
其实看看printf中后面的一句:LLprint(buf, i);
介个是干啥的?
什么,你不知道,那赶紧看上一篇文章。
总结: vsLprintf的作用就是格式化。
它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出。
我们也可以看看一个串口的printf的实现:
- //*****************************************************************************
- //
- //! A simple UART based printf function supporting \%c, \%d, \%p, \%s, \%u,
- //! \%x, and \%X.
- //!
- //! \param pcString is the format string.
- //! \param ... are the optional arguments, which depend on the contents of the
- //! format string.
- //!
- //! This function is very similar to the C library <tt>fprintf()</tt> function.
- //! All of its output will be sent to the UART. Only the following formatting
- //! characters are supported:
- //!
- //! - \%c to print a character
- //! - \%d to print a decimal value
- //! - \%s to print a string
- //! - \%u to print an unsigned decimal value
- //! - \%x to print a hexadecimal value using lower case letters
- //! - \%X to print a hexadecimal value using lower case letters (not upper case
- //! letters as would typically be used)
- //! - \%p to print a pointer as a hexadecimal value
- //! - \%\% to print out a \% character
- //!
- //! For \%s, \%d, \%u, \%p, \%x, and \%X, an optional number may reside
- //! between the \% and the format character, which specifies the minimum number
- //! of characters to use for that value; if preceded by a 0 then the extra
- //! characters will be filled with zeros instead of spaces. For example,
- //! ``\%8d'' will use eight characters to print the decimal value with spaces
- //! added to reach eight; ``\%08d'' will use eight characters as well but will
- //! add zeroes instead of spaces.
- //!
- //! The type of the arguments after \e pcString must match the requirements of
- //! the format string. For example, if an integer was passed where a string
- //! was expected, an error of some kind will most likely occur.
- //!
- //! \return None.
- //
- //*****************************************************************************
- void
- UARTprintf(const char *pcString, ...)
- {
- unsigned long ulIdx, ulValue, ulPos, ulCount, ulBase, ulNeg;
- char *pcStr, pcBuf[16], cFill;
- va_list vaArgP;
- //
- // Check the arguments.
- //
- ASSERT(pcString != 0);
- //
- // Start the varargs processing.
- //
- va_start(vaArgP, pcString);
- //
- // Loop while there are more characters in the string.
- //
- while(*pcString)
- {
- //
- // Find the first non-% character, or the end of the string.
- //
- for(ulIdx = 0; (pcString[ulIdx] != '%') && (pcString[ulIdx] != '\0');
- ulIdx++)
- {
- }
- //
- // Write this portion of the string.
- //
- UARTwrite(pcString, ulIdx);
- //
- // Skip the portion of the string that was written.
- //
- pcString += ulIdx;
- //
- // See if the next character is a %.
- //
- if(*pcString == '%')
- {
- //
- // Skip the %.
- //
- pcString++;
- //
- // Set the digit count to zero, and the fill character to space
- // (i.e. to the defaults).
- //
- ulCount = 0;
- cFill = ' ';
- //
- // It may be necessary to get back here to process more characters.
- // Goto's aren't pretty, but effective. I feel extremely dirty for
- // using not one but two of the beasts.
- //
- again:
- //
- // Determine how to handle the next character.
- //
- switch(*pcString++)
- {
- //
- // Handle the digit characters.
- //
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- {
- //
- // If this is a zero, and it is the first digit, then the
- // fill character is a zero instead of a space.
- //
- if((pcString[-1] == '0') && (ulCount == 0))
- {
- cFill = '0';
- }
- //
- // Update the digit count.
- //
- ulCount *= 10;
- ulCount += pcString[-1] - '0';
- //
- // Get the next character.
- //
- goto again;
- }
- //
- // Handle the %c command.
- //
- case 'c':
- {
- //
- // Get the value from the varargs.
- //
- ulValue = va_arg(vaArgP, unsigned long);
- //
- // Print out the character.
- //
- UARTwrite((char *)&ulValue, 1);
- //
- // This command has been handled.
- //
- break;
- }
- //
- // Handle the %d command.
- //
- case 'd':
- {
- //
- // Get the value from the varargs.
- //
- ulValue = va_arg(vaArgP, unsigned long);
- //
- // Reset the buffer position.
- //
- ulPos = 0;
- //
- // If the value is negative, make it positive and indicate
- // that a minus sign is needed.
- //
- if((long)ulValue < 0)
- {
- //
- // Make the value positive.
- //
- ulValue = -(long)ulValue;
- //
- // Indicate that the value is negative.
- //
- ulNeg = 1;
- }
- else
- {
- //
- // Indicate that the value is positive so that a minus
- // sign isn't inserted.
- //
- ulNeg = 0;
- }
- //
- // Set the base to 10.
- //
- ulBase = 10;
- //
- // Convert the value to ASCII.
- //
- goto convert;
- }
- //
- // Handle the %s command.
- //
- case 's':
- {
- //
- // Get the string pointer from the varargs.
- //
- pcStr = va_arg(vaArgP, char *);
- //
- // Determine the length of the string.
- //
- for(ulIdx = 0; pcStr[ulIdx] != '\0'; ulIdx++)
- {
- }
- //
- // Write the string.
- //
- UARTwrite(pcStr, ulIdx);
- //
- // Write any required padding spaces
- //
- if(ulCount > ulIdx)
- {
- ulCount -= ulIdx;
- while(ulCount--)
- {
- UARTwrite(" ", 1);
- }
- }
- //
- // This command has been handled.
- //
- break;
- }
- //
- // Handle the %u command.
- //
- case 'u':
- {
- //
- // Get the value from the varargs.
- //
- ulValue = va_arg(vaArgP, unsigned long);
- //
- // Reset the buffer position.
- //
- ulPos = 0;
- //
- // Set the base to 10.
- //
- ulBase = 10;
- //
- // Indicate that the value is positive so that a minus sign
- // isn't inserted.
- //
- ulNeg = 0;
- //
- // Convert the value to ASCII.
- //
- goto convert;
- }
- //
- // Handle the %x and %X commands. Note that they are treated
- // identically; i.e. %X will use lower case letters for a-f
- // instead of the upper case letters is should use. We also
- // alias %p to %x.
- //
- case 'x':
- case 'X':
- case 'p':
- {
- //
- // Get the value from the varargs.
- //
- ulValue = va_arg(vaArgP, unsigned long);
- //
- // Reset the buffer position.
- //
- ulPos = 0;
- //
- // Set the base to 16.
- //
- ulBase = 16;
- //
- // Indicate that the value is positive so that a minus sign
- // isn't inserted.
- //
- ulNeg = 0;
- //
- // Determine the number of digits in the string version of
- // the value.
- //
- convert:
- for(ulIdx = 1;
- (((ulIdx * ulBase) <= ulValue) &&
- (((ulIdx * ulBase) / ulBase) == ulIdx));
- ulIdx *= ulBase, ulCount--)
- {
- }
- //
- // If the value is negative, reduce the count of padding
- // characters needed.
- //
- if(ulNeg)
- {
- ulCount--;
- }
- //
- // If the value is negative and the value is padded with
- // zeros, then place the minus sign before the padding.
- //
- if(ulNeg && (cFill == '0'))
- {
- //
- // Place the minus sign in the output buffer.
- //
- pcBuf[ulPos++] = '-';
- //
- // The minus sign has been placed, so turn off the
- // negative flag.
- //
- ulNeg = 0;
- }
- //
- // Provide additional padding at the beginning of the
- // string conversion if needed.
- //
- if((ulCount > 1) && (ulCount < 16))
- {
- for(ulCount--; ulCount; ulCount--)
- {
- pcBuf[ulPos++] = cFill;
- }
- }
- //
- // If the value is negative, then place the minus sign
- // before the number.
- //
- if(ulNeg)
- {
- //
- // Place the minus sign in the output buffer.
- //
- pcBuf[ulPos++] = '-';
- }
- //
- // Convert the value into a string.
- //
- for(; ulIdx; ulIdx /= ulBase)
- {
- pcBuf[ulPos++] = g_pcHex[(ulValue / ulIdx) % ulBase];
- }
- //
- // Write the string.
- //
- UARTwrite(pcBuf, ulPos);
- //
- // This command has been handled.
- //
- break;
- }
- //
- // Handle the %% command.
- //
- case '%':
- {
- //
- // Simply write a single %.
- //
- UARTwrite(pcString - 1, 1);
- //
- // This command has been handled.
- //
- break;
- }
- //
- // Handle all other commands.
- //
- default:
- {
- //
- // Indicate an error.
- //
- UARTwrite("ERROR", 5);
- //
- // This command has been handled.
- //
- break;
- }
- }
- }
- }
- //
- // End the varargs processing.
- //
- va_end(vaArgP);
- }
写的很精彩,是不是?
五、应用层的使用
通过上面的工作,我们已经实现了一个自己的printf函数: LLprintf
LLprintf的功能和我们标准库的printf一样强大,我们可以在上层如此使用LLprintf:
- /************************************************************************************
- ** File: - Z:\code\c\LLprintf\print2.1\app.c
- **
- ** Copyright (C), Long.Luo, All Rights Reserved!
- **
- ** Description:
- ** app.c --- The Application Level.
- **
- ** Version: 2.1
- ** Date created: 23:53:41,24/01/2013
- ** Author: Long.Luo
- **
- ** --------------------------- Revision History: --------------------------------
- ** <author> <data> <desc>
- **
- ************************************************************************************/
- #include "LLprintf.h"
- int main(void)
- {
- char *welcome = " A Tiny Demo show the LLprintf ";
- char *program_name = "LLprintf";
- char *program_author = "Long.Luo";
- char *date = "Jan. 24th, 2013";
- float program_version = 2.1;
- Lprintf("%s\n\n", welcome);
- Lprintf("\t\t%s, version %f \n\n", program_name, program_version);
- Lprintf("\tCreated by %s, %s.\n\n", program_author, date);
- return 0;
- }
make一下,我们再来看看输出结果:
这样我们就了解了printf函数的前因后果,聪明的你,弄明白了吗?^_^
- 深入剖析printf函数(下):---形参列表和格式化输出是如何做到的?
- 深入剖析printf函数(下):---形参列表和格式化输出是如何做到的?
- 格式化输出函数printf
- 格式化输出函数printf()
- 格式化输出的函数printf()用法
- C语言的格式化输出函数printf
- printf函数实现的深入剖析
- printf函数的实现深入剖析
- printf函数实现的深入剖析
- printf函数实现的深入剖析
- printf函数实现的深入剖析
- printf 函数实现的深入剖析[转载]
- printf 函数实现的深入剖析
- printf()的格式化输出
- printf的格式化输出
- printf函数常用的输出方式及格式化输出说明
- 深入剖析printf函数(上):如何不借助第三方库在屏幕上输出"Hello World"?
- 深入剖析printf函数(上):如何不借助第三方库在屏幕上输出"Hello World"?
- 第一次深深感到进步
- 升级app时coreData的处理及注意事项
- Ubuntu 自动安装libsvm
- HDU 3951 Coin Game 简单博弈
- SizeClass With Xcode6(Xcode6 中使用SizeClass布局)
- 深入剖析printf函数(下):---形参列表和格式化输出是如何做到的?
- 第六天
- POJ1178枚举三个地方(所有点都去同一个点)
- HNU Number Guessing
- python 进阶4 笔记
- Framework启动过程
- flexibility of openstack(4)
- Android UI设计: 仿ios,仿qq实现Listview侧滑出现按钮
- [BZOJ 2456] mode · 模拟