1 /* 
  2 * ===================================================================================== 
  3 * 
  4 *       Filename:  printf.c 
  5 * 
  6 *    Description:  printf 函数的实现 
  7 * 
  8 *        Version:  1.0 
  9 *        Created:  2010年12月12日 14时48分18秒 
 10 *       Revision:  none 
 11 *       Compiler:  gcc 
 12 * 
 13 *         Author:  Yang Shao Kun (), cdutyangshaokun@163.com 
 14 *        Company:  College of Information Engineering of CDUT 
 15 * 
 16 * ===================================================================================== 
 17 */
 18 要了解变参函数的实现,首先我们的弄清楚几个问题: 
 19 1:该函数有几个参数。 
 20 2:该函数增样去访问这些参数。 
 21 3:在访问完成后,如何从堆栈中释放这些参数。
 22 对于c语言,它的调用规则遵循_cdedl调用规则。 
 23 在_cdedl规则中:1.参数从右到左依次入栈 
 24                 2.调用者负责清理堆栈 
 25                 3.参数的数量类型不会导致编译阶段的错误 
 26 要弄清楚变参函数的原理,我们需要解决上述的3个问题,其中的第三个问题,根据调 
 27 用原则,那我们现在可以不管。
 28 要处理变参函数,需要用到 va_list 类型,和 va_start,va_end,va_arg 宏定义。我 
 29 看网上的许多资料说这些参数都是定义在stdarg.h这个头文件中,但是在我的linux机 
 30 器上,我的版本是fedorea 14,用vim访问的时候,确是在 acenv.h这个头文件中,估 
 31 计是内核的版本不一样的原因吧!!!
 32 上面的这几个宏和其中的类型,在内核中是这样来实现的:
 33 #ifndef _VALIST 
 34 #define _VALIST 
 35 typedef char *va_list; 
 36 #endif                /* _VALIST */
 37 /* 
 38 * Storage alignment properties 
 39 */ 
 40 #define  _AUPBND                (sizeof (acpi_native_int) - 1) 
 41 #define  _ADNBND                (sizeof (acpi_native_int) - 1)
 42 /* 
 43 * Variable argument list macro definitions 
 44 */ 
 45 #define _bnd(X, bnd)            (((sizeof (X)) + (bnd)) & (~(bnd))) 
 46 #define va_arg(ap, T)           (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND)))) 
 47 #define va_end(ap)              (void) 0 
 48 #define va_start(ap, A)         (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))
 49 #endif                /* va_arg */
 50 首先来看 va_list 类型,其实这是一个字符指针。
 51 va_start,是使ap指针指向变参函数中的下一个参数。
 52 我们现在来看_bnd 宏的实现: 
 53 首先: 
 54 typedef s32 acpi_native_int; 
 55 typedef int            s32; 
 56 看出来,acpi_native_int 其实就是 int 类型,那么,
 57 #define  _AUPBND                (sizeof (acpi_native_int) - 1) 
 58 #define  _ADNBND                (sizeof (acpi_native_int) - 1) 
 59 这两个值就应该是相等的,都-等于:3==0x00000003,按位取反后的结果就是:0xfffff 
 60 ffc,因此, 
 61 _bnd(x,bnd)宏在32位机下就是 
 62 (((sizeof (X)) + (3)) & (0xfffffffc)),那么作用就很明显是取4的整数,就相当与 
 63 整数除法后取ceiling--向上取整。
 64 回过头来看 va_start(ap,A),初始化参数指针ap,将函数参数A右边右边第一个参数地 
 65 址赋值给ap,A必须是一个参数的指针,所以,此种类型函数至少要有一个普通的参数 
 66 ,从而提供给va_start ,这样va_start才能找到可变参数在栈上的位置。 
 67 va_arg(ap,T),获得ap指向参数的值,同时使ap指向下一个参数,T用来指名当前参数类 
 68 型。 
 69 va_end 在有些简单的实现中不起任何作用,在有些实现中可能会把ap改成无效值,这 
 70 里,是把ap指针指向了 NULL。 
 71 c标准要求在同一个函数中va_start 和va_end 要配对的出现。
 72 那么到现在,处理多参数函数的步骤就是 
 73 1:首先是要保证该函数至少有一个参数,同时用...参数申明函数是变参函数。 
 74 2:在函数内部以va_start(ap,A)宏初始化参数指针。 
 75 3:用va_arg(ap,T)从左到右逐个取参数值。
 76 printf()格式转换的一般形式如下: 
 77 %[flags][width][.prec][type] 
 78 prec有一下几种情况: 
 79                     正整数的最小位数 
 80                     在浮点数中表示的小数位数 
 81                     %g格式表示有效为的最大值 
 82                     %s格式表示字符串的最大长度 
 83                     若为*符号表示下个参数值为最大长度 
 84 width:为输出的最小长度,如果这个输出参数并非数值,而是*符号,则表示以下一个参数当做输出长度。
 85 现在来看看我们的printf函数的实现,在内核中printf函数被封装成下面的代码: 
 86 static char sprint_buf[1024];
 87 int printf(const char *fmt, ...) 
 88 { 
 89     va_list args; 
 90     int n;
 91     va_start(args, fmt);//初始化参数指针 
 92     n = vsprintf(sprint_buf, fmt, args);/*函数放回已经处理的字符串长度*/ 
 93     va_end(args);//与va_start 配对出现,处理ap指针 
 94     if (console_ops.write) 
 95         console_ops.write(sprint_buf, n);/*调用控制台的结构中的write函数,将sprintf_buf中的内容输出n个字节到设备*/
 96     return n; 
 97 } 
 98 vs_printf函数的实现代码是: 
 99 int vsprintf(char *buf, const char *fmt, va_list args) 
100 { 
101     int len; 
102     unsigned long long num; 
103     int i, base; 
104     char * str; 
105     const char *s;/*s所指向的内存单元不可改写,但是s可以改写*/
106     int flags;        /* flags to number() */
107     int field_width;    /* width of output field */ 
108     int precision;        /* min. # of digits for integers; max 
109                    number of chars for from string */ 
110     int qualifier;        /* 'h', 'l', or 'L' for integer fields */ 
111                             /* 'z' support added 23/7/1999 S.H.    */ 
112                 /* 'z' changed to 'Z' --davidm 1/25/99 */
113     for (str=buf ; *fmt ; ++fmt) 
114     { 
115         if (*fmt != '%') /*使指针指向格式控制符'%,以方便以后处理flags'*/ 
116         { 
117             *str++ = *fmt; 
118             continue; 
119         } 
120         /* process flags */ 
121         flags = 0; 
122         repeat: 
123             ++fmt;        /* this also skips first '%'--跳过格式控制符'%' */ 
124             switch (*fmt) 
125             { 
126                 case '-': flags |= LEFT; goto repeat;/*左对齐-left justify*/ 
127                 case '+': flags |= PLUS; goto repeat;/*p plus with ’+‘*/ 
128                 case ' ': flags |= SPACE; goto repeat;/*p with space*/ 
129                 case '#': flags |= SPECIAL; goto repeat;/*根据其后的转义字符的不同而有不同含义*/ 
130                 case '0': flags |= ZEROPAD; goto repeat;/*当有指定参数时,无数字的参数将补上0*/ 
131             } 
132 //#define ZEROPAD    1        /* pad with zero */ 
133 //#define SIGN    2        /* unsigned/signed long */ 
134 //#define PLUS    4        /* show plus */ 
135 //#define SPACE    8        /* space if plus */ 
136 //#define LEFT    16        /* left justified */ 
137 //#define SPECIAL    32        /* 0x */ 
138 //#define LARGE    64        /* use 'ABCDEF' instead of 'abcdef' */ 
139         /* get field width ----deal 域宽 取当前参数字段宽度域值,放入field_width 变量中。如果宽度域中是数值则直接取其为宽度值。 如果宽度域中是字符'*',表示下一个参数指定宽度。因此调用va_arg 取宽度值。若此时宽度值小于0,则该负数表示其带有标志域'-'标志(左靠齐),因此还需在标志变量中添入该标志,并将字段宽度值取为其绝对值。  */ 
140         field_width = -1; 
141         if ('0' <= *fmt && *fmt <= '9') 
142             field_width = skip_atoi(&fmt); 
143         else if (*fmt == '*') 
144         { 
145             ++fmt;/*skip '*' */ 
146             /* it's the next argument */ 
147             field_width = va_arg(args, int); 
148             if (field_width < 0) { 
149                 field_width = -field_width; 
150                 flags |= LEFT; 
151             } 
152         }
153         /* get the precision-----即是处理.pre 有效位 */ 
154         precision = -1; 
155         if (*fmt == '.') 
156         { 
157             ++fmt;    
158             if ('0' <= *fmt && *fmt <= '9') 
159                 precision = skip_atoi(&fmt); 
160             else if (*fmt == '*') /*如果精度域中是字符'*',表示下一个参数指定精度。因此调用va_arg 取精度值。若此时宽度值小于0,则将字段精度值取为0。*/ 
161             { 
162                 ++fmt; 
163                 /* it's the next argument */ 
164                 precision = va_arg(args, int); 
165             } 
166             if (precision < 0) 
167                 precision = 0; 
168         }
169         /* get the conversion qualifier 分析长度修饰符,并将其存入qualifer 变量*/ 
170         qualifier = -1; 
171         if (*fmt == 'l' && *(fmt + 1) == 'l') 
172         { 
173             qualifier = 'q'; 
174             fmt += 2; 
175         } 
176         else if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L'|| *fmt == 'Z') 
177         { 
178             qualifier = *fmt; 
179             ++fmt; 
180         }
181         /* default base */ 
182         base = 10; 
183         /*处理type部分*/ 
184         switch (*fmt) 
185         { 
186             case 'c': 
187                 if (!(flags & LEFT))/*没有左对齐标志,那么填充field_width-1个空格*/ 
188                     while (--field_width > 0) 
189                     *str++ = ' '; 
190                     *str++ = (unsigned char) va_arg(args, int); 
191                     while (--field_width > 0)/*不是左对齐*/ 
192                     *str++ = ' ';/*在参数后输出field_width-1个空格*/ 
193                     continue; 
194             /*如果转换参数是s,则,表示对应的参数是字符串,首先取参数字符串的长度,如果超过了精度域值,则取精度域值为最大长度*/ 
195             case 's': 
196                 s = va_arg(args, char *); 
197                 if (!s) 
198                     s = "";
199                     len = strnlen(s, precision);/*字符串的长度,最大为precision*/
200                     if (!(flags & LEFT)) 
201                     while (len < field_width--)/*如果不是左对齐,则左侧补空格=field_width-len个空格*/ 
202                     *str++ = ' '; 
203                     for (i = 0; i < len; ++i) 
204                     *str++ = *s++; 
205                     while (len < field_width--)/*如果是左对齐,则右侧补空格数=field_width-len*/ 
206                     *str++ = ' '; 
207                     continue; 
208 /*如果格式转换符是'p',表示对应参数的一个指针类型。此时若该参数没有设置宽度域,则默认宽度为8,并且需要添零。然后调用number()*/ 
209             case 'p': 
210                 if (field_width == -1) 
211                 { 
212                     field_width = 2*sizeof(void *); 
213                     flags |= ZEROPAD; 
214                 } 
215                 str = number(str,(unsigned long) va_arg(args, void *), 16, 
216                         field_width, precision, flags); 
217                 continue;
218         // 若格式转换指示符是'n',则表示要把到目前为止转换输出的字符数保存到对应参数指针指定的位置中。  
219         // 首先利用va_arg()得该参数指针,然后将已经转换好的字符数存入该指针所指的位置 
220             case 'n': 
221             if (qualifier == 'l') 
222             { 
223                 long * ip = va_arg(args, long *); 
224                 *ip = (str - buf); 
225             } 
226             else if (qualifier == 'Z') 
227             { 
228                 size_t * ip = va_arg(args, size_t *); 
229                 *ip = (str - buf); 
230             } 
231             else 
232             { 
233                 int * ip = va_arg(args, int *); 
234                 *ip = (str - buf); 
235             } 
236             continue; 
237     //若格式转换符不是'%',则表示格式字符串有错,直接将一个'%'写入输出串中。  
238     // 如果格式转换符的位置处还有字符,则也直接将该字符写入输出串中,并返回到继续处理  
239     //格式字符串。 
240             case '%': 
241             *str++ = '%'; 
242             continue;
243             /* integer number formats - set up the flags and "break" */ 
244             case 'o': 
245             base = 8; 
246             break;
247             case 'X': 
248             flags |= LARGE; 
249             case 'x': 
250             base = 16; 
251             break; 
252 // 如果格式转换字符是'd','i'或'u',则表示对应参数是整数,'d', 'i'代表符号整数,因此需要加上  
253 // 带符号标志。'u'代表无符号整数 
254             case 'd': 
255             case 'i': 
256             flags |= SIGN; 
257             case 'u': 
258             break;
259             default: 
260             *str++ = '%'; 
261             if (*fmt) 
262                 *str++ = *fmt; 
263             else 
264                 --fmt; 
265             continue; 
266         }
267         /*处理字符的修饰符,同时如果flags有符号位的话,将参数转变成有符号的数*/ 
268         if (qualifier == 'l') 
269         { 
270             num = va_arg(args, unsigned long); 
271             if (flags & SIGN) 
272                 num = (signed long) num; 
273         } 
274         else if (qualifier == 'q') 
275         { 
276             num = va_arg(args, unsigned long long); 
277             if (flags & SIGN) 
278                 num = (signed long long) num; 
279         } 
280         else if (qualifier == 'Z') 
281         { 
282             num = va_arg(args, size_t); 
283         } 
284         else if (qualifier == 'h') 
285         { 
286             num = (unsigned short) va_arg(args, int); 
287             if (flags & SIGN) 
288                 num = (signed short) num; 
289         } 
290         else 
291         { 
292             num = va_arg(args, unsigned int); 
293             if (flags & SIGN) 
294                 num = (signed int) num; 
295         } 
296         str = number(str, num, base, field_width, precision, flags); 
297     } 
298     *str = '/0';/*最后在转换好的字符串上加上NULL*/ 
299     return str-buf;/*返回转换好的字符串的长度值*/ 
300 }
View Code

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2022-01-09
  • 2021-09-16
  • 2022-12-23
  • 2021-08-15
  • 2021-08-30
猜你喜欢
  • 2021-07-07
  • 2021-07-07
  • 2021-09-29
  • 2022-12-23
  • 2021-07-10
  • 2022-12-23
相关资源
相似解决方案