一、可变参数函数实现原理

C函数调用的栈结构:

可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈。

本文地址:http://www.cnblogs.com/archimedes/p/variable-parameter.html,转载请注明源地址。

例如,对于函数:

  1. void fun(int a, int b, int c)
  2. {
  3. int d;
  4. ...
  5. }

其栈结构为

0x1ffc-->d

0x2000-->a

0x2004-->b

0x2008-->c

对于在32位系统的多数编译器,每个栈单元的大小都是sizeof(int), 而函数的每个参数都至少要占一个栈单元大小,如函数 void fun1(char a, int b, double c, short d) 对一个32的系统其栈的结构就是

0x1ffc-->a  (4字节)(为了字对齐)

0x2000-->b  (4字节)

0x2004-->c  (8字节)

0x200c-->d  (4字节)

因此,函数的所有参数是存储在线性连续的栈空间中的,基于这种存储结构,这样就可以从可变参数函数中必须有的第一个普通参数来寻址后续的所有可变参数的类型及其值。

先看看固定参数列表函数:

  1. void fixed_args_func(int a, double b, char *c)
  2. {
  3. printf("a = 0x%p\n", &a);
  4. printf("b = 0x%p\n", &b);
  5. printf("c = 0x%p\n", &c);
  6. }

对于固定参数列表的函数,每个参数的名称、类型都是直接可见的,他们的地址也都是可以直接得到的,比如:通过&a我们可以得到a的地址,并通过函数原型声明了解到a是int类型的。

但是对于变长参数的函数,我们就没有这么顺利了。还好,按照C标准的说明,支持变长参数的函数在原型声明中,必须有至少一个最左固定参数(这一点与传统C有区别,传统C允许不带任何固定参数的纯变长参数函数),这样我们可以得到其中固定参数的地址,但是依然无法从声明中得到其他变长参数的地址,比如:

  1. void var_args_func(const char * fmt, ...)
  2. {
  3. ... ...
  4. }

这里我们只能得到fmt这固定参数的地址,仅从函数原型我们是无法确定"..."中有几个参数、参数都是什么类型的。回想一下函数传参的过程,无论"..."中有多少个参数、每个参数是什么类型的,它们都和固定参数的传参过程是一样的,简单来讲都是栈操作,而栈这个东西对我们是开放的。这样一来,一旦我们知道某函数帧的栈上的一个固定参数的位置,我们完全有可能推导出其他变长参数的位置。

我们先用上面的那个fixed_args_func函数确定一下入栈顺序。

  1. int main()
  2. {
  3. fixed_args_func(, 5.40, "hello world");
  4. return ;
  5. }
  6. a = 0x0022FF50
  7. b = 0x0022FF54
  8. c = 0x0022FF5C

从这个结果来看,显然参数是从右到左,逐一压入栈中的(栈的延伸方向是从高地址到低地址,栈底的占领着最高内存地址,先入栈的参数,其地理位置也就最高了)。

我们基本可以得出这样一个结论:

  1. c.addr = b.addr + x_sizeof(b); /*注意: x_sizeof !=sizeof */
  2. b.addr = a.addr + x_sizeof(a);

有了以上的"等式",我们似乎可以推导出 void var_args_func(const char * fmt, ... ) 函数中,可变参数的位置了。起码第一个可变参数的位置应该是:first_vararg.addr = fmt.addr + x_sizeof(fmt);  根据这一结论我们试着实现一个支持可变参数的函数:

  1. #include <stdarg.h>
  2. #include <stdio.h>
  3.  
  4. void var_args_func(const char * fmt, ...)
  5. {
  6. char *ap;
  7.  
  8. ap = ((char*)&fmt) + sizeof(fmt);
  9. printf("%d\n", *(int*)ap);
  10.  
  11. ap = ap + sizeof(int);
  12. printf("%d\n", *(int*)ap);
  13.  
  14. ap = ap + sizeof(int);
  15. printf("%s\n", *((char**)ap));
  16. }
  17.  
  18. int main()
  19. {
  20. var_args_func("%d %d %s\n", , , "hello world");
  21.    return ;
  22. }

期待输出结果:

4
5
hello world


先来解释一下这个程序。我们用ap获取第一个变参的地址,我们知道第一个变参是4,一个int 型,所以我们用(int*)ap以告诉编译器,以ap为首地址的那块内存我们要将之视为一个整型来使用,*(int*)ap获得该参数的值;接下来的变参是5,又一个int型,其地址是ap + sizeof(第一个变参),也就是ap + sizeof(int),同样我们使用*(int*)ap获得该参数的值;最后的一个参数是一个字符串,也就是char*,与前两个int型参数不同的是,经过ap + sizeof(int)后,ap指向栈上一个char*类型的内存块(我们暂且称之tmp_ptr, char *tmp_ptr)的首地址,即ap -> &tmp_ptr,而我们要输出的不是printf("%s\n", ap),而是printf("%s\n", tmp_ptr); printf("%s\n", ap)是意图将ap所指的内存块作为字符串输出了,但是ap -> &tmp_ptr,tmp_ptr所占据的4个字节显然不是字符串,而是一个地址。如何让&tmp_ptr是char **类型的,我们将ap进行强制转换(char**)ap <=> &tmp_ptr,这样我们访问tmp_ptr只需要在(char**)ap前面加上一个*即可,即printf("%s\n",  *(char**)ap);


一切似乎很完美,编译也很顺利通过,但运行上面的代码后,不但得不到预期的结果,反而整个编译器会强行关闭(大家可以尝试着运行一下),原来是ap指针在后来并没有按照预期的要求指向第二个变参数,即并没有指向5所在的首地址,而是指向了未知内存区域,所以编译器会强行关闭。其实错误开始于:ap =  ap + sizeof(int);由于内存对齐,编译器在栈上压入参数时,不是一个紧挨着另一个的,编译器会根据变参的类型将其放到满足类型对齐的地址上的,这样栈上参数之间实际上可能会是有空隙的。参见:(C语言内存对齐),所以此时的ap计算应该改为:ap =  (char *)ap +sizeof(int) + __va_rounded_size(int);

改正后的代码如下:

  1. #include<stdio.h>
  2.  
  3. #define __va_rounded_size(TYPE) \
  4. (((sizeof (TYPE) + sizeof (int) - ) / sizeof (int)) * sizeof (int))
  5.  
  6. void var_args_func(const char * fmt, ...)
  7. {
  8. char *ap;
  9.  
  10. ap = ((char*)&fmt) + sizeof(fmt);
  11. printf("%d\n", *(int*)ap);
  12.  
  13. ap = (char *)ap + sizeof(int) + __va_rounded_size(int);
  14. printf("%d\n", *(int*)ap);
  15.  
  16. ap = ap + sizeof(int) + __va_rounded_size(int);
  17. printf("%s\n", *((char**)ap));
  18. }
  19.  
  20. int main()
  21. {
  22. var_args_func("%d %d %s\n", , , "hello world"); 
  23. return ;
  24. }

var_args_func只是为了演示,并未根据fmt消息中的格式字符串来判断变参的个数和类型,而是直接在实现中写死了。

为了满足代码的可移植性,C标准库在stdarg.h中提供了诸多便利以供实现变长长度参数时使用。这里也列出一个简单的例子,看看利用标准库是如何支持变长参数的:

  1. #include <stdarg.h>
  2. #include <stdio.h>
  3. void std_vararg_func(const char *fmt, ...) {
  4. va_list ap;
  5. va_start(ap, fmt);
  6. printf("%d\n", va_arg(ap, int));
  7. printf("%f\n", va_arg(ap, double));
  8. printf("%s\n", va_arg(ap, char*));
  9.  
  10. va_end(ap);
  11. }
  12.  
  13. int main()
  14. {
  15. std_vararg_func("%d %f %s\n", , 5.4, "hello world");
  16. return ;
  17. }

对比一下 std_vararg_func和var_args_func的实现,va_list似乎就是char*, va_start似乎就是 ((char*)&fmt) + sizeof(fmt),va_arg似乎就是得到下一个参数的首地址。没错,多数平台下stdarg.h中va_list, va_start和var_arg的实现就是类似这样的。一般stdarg.h会包含很多宏,看起来比较复杂。

下面我们来探讨如何写一个简单的可变参数的C 函数.

使用可变参数应该有以下步骤: 
1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针. 
2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数. 
3)然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个参数是你要返回的参数的类型,这里是int型. 
4)最后用va_end宏结束可变参数的获取.然后你就可以在函数里使用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获取各个参数.

在《C程序设计语言》中,Ritchie提供了一个简易版printf函数:

  1. #include<stdarg.h>
  2.  
  3. void minprintf(char *fmt, ...)
  4. {
  5. va_list ap;
  6. char *p, *sval;
  7. int ival;
  8. double dval;
  9.  
  10. va_start(ap, fmt);
  11. for (p = fmt; *p; p++) {
  12. if(*p != '%') {
  13. putchar(*p);
  14. continue;
  15. }
  16. switch(*++p) {
  17. case 'd':
  18. ival = va_arg(ap, int);
  19. printf("%d", ival);
  20. break;
  21. case 'f':
  22. dval = va_arg(ap, double);
  23. printf("%f", dval);
  24. break;
  25. case 's':
  26. for (sval = va_arg(ap, char *); *sval; sval++)
  27. putchar(*sval);
  28. break;
  29. default:
  30. putchar(*p);
  31. break;
  32. }
  33. }
  34. va_end(ap);
  35. }

二、stdarg.h头文件源代码分析

谈到C语言中可变参数函数的实现,有一个头文件不得不谈,那就是stdarg.h

接下来从minix源码中的stdarg.h头文件入手进行分析:

  1. #ifndef _STDARG_H
  2. #define _STDARG_H
  3.  
  4. #ifdef __GNUC__
  5. /* The GNU C-compiler uses its own, but similar varargs mechanism. */
  6.  
  7. typedef char *va_list;
  8.  
  9. /* Amount of space required in an argument list for an arg of type TYPE.
  10. * TYPE may alternatively be an expression whose type is used.
  11. */
  12.  
  13. #define __va_rounded_size(TYPE) \
  14. (((sizeof (TYPE) + sizeof (int) - ) / sizeof (int)) * sizeof (int))
  15.  
  16. #if __GNUC__ < 2
  17.  
  18. #ifndef __sparc__
  19. #define va_start(AP, LASTARG) \
  20. (AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
  21. #else
  22. #define va_start(AP, LASTARG) \
  23. (__builtin_saveregs (), \
  24. AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
  25. #endif
  26.  
  27. void va_end (va_list); /* Defined in gnulib */
  28. #define va_end(AP)
  29.  
  30. #define va_arg(AP, TYPE) \
  31. (AP += __va_rounded_size (TYPE), \
  32. *((TYPE *) (AP - __va_rounded_size (TYPE))))
  33.  
  34. #else /* __GNUC__ >= 2 */
  35.  
  36. #ifndef __sparc__
  37. #define va_start(AP, LASTARG) \
  38. (AP = ((char *) __builtin_next_arg ()))
  39. #else
  40. #define va_start(AP, LASTARG) \
  41. (__builtin_saveregs (), AP = ((char *) __builtin_next_arg ()))
  42. #endif
  43.  
  44. void va_end (va_list); /* Defined in libgcc.a */
  45. #define va_end(AP)
  46.  
  47. #define va_arg(AP, TYPE) \
  48. (AP = ((char *) (AP)) += __va_rounded_size (TYPE), \
  49. *((TYPE *) ((char *) (AP) - __va_rounded_size (TYPE))))
  50.  
  51. #endif /* __GNUC__ >= 2 */
  52.  
  53. #else /* not __GNUC__ */
  54.  
  55. typedef char *va_list;
  56.  
  57. #define __vasz(x) ((sizeof(x)+sizeof(int)-1) & ~(sizeof(int) -1))
  58.  
  59. #define va_start(ap, parmN) ((ap) = (va_list)&parmN + __vasz(parmN))
  60. #define va_arg(ap, type) \
  61. (*((type *)((va_list)((ap) = (void *)((va_list)(ap) + __vasz(type))) \
  62. - __vasz(type))))
  63. #define va_end(ap)
  64.  
  65. #endif /* __GNUC__ */
  66.  
  67. #endif /* _STDARG_H */

从代码中可以看到,里面编译器的版本以及相关的大量宏定义

第5行: #ifdef __GNUC__
作用是条件编译,__GNUC__为GCC中定义的宏。GCC的版本,为一个整型值。如果你需要知道自己的程序是否被GCC编译,可以简单的测试一下__GNUC__,假如你代码需要运行在GCC某个特定的版本下,那么你就要小心了,因为GCC的主要版本在增加,如果你想定义宏的方式直接实现控制,你可以写如下的代码(参见伯克利大学网站):

  1. /* 测试 GCC > 3.2.0 ? */
  2. #if __GNUC__ > 3 || \
  3. (__GNUC__ == && (__GNUC_MINOR__ > || \
  4. (__GNUC_MINOR__ == && \
  5. __GNUC_PATCHLEVEL__ > ))

你还可以使用下面一个类似的方法:

  1. #define GCC_VERSION (__GNUC__ * 10000 \
  2. + __GNUC_MINOR__ * \
  3. + __GNUC_PATCHLEVEL__)
  4. ...
  5. /*测试 GCC > 3.2.0 ?*/
  6. #if GCC_VERSION > 30200

第8行: 使用typedef进行了一个声明:typedef char *va_list;

第14行:定义了用于编译器的内存对齐宏,参见:(C语言内存对齐),

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

第17行:#if __GNUC__ < 2,进行GCC的版本判断,看当前版本是否大于2

第19行:#ifndef __sparc__ 可扩充处理器架构宏(以后再深入研究)

第20行:使得ap指向函数中的第一个无名参数的首地址的宏:

  1. #define va_start(AP, LASTARG) \
  2. (AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))

第31行:

  1. #define va_arg(AP, TYPE) \
  2. (AP += __va_rounded_size (TYPE), \
  3. *((TYPE *) (AP - __va_rounded_size (TYPE))))

va_arg宏使得ap指向下一个参数,已经处理了内存对齐,其中参数的类型为TYPE

第48行:

  1. void va_end (va_list); /* Defined in gnulib */

定义在gnulib中,va_end 与va_start成对使用.在有些代码中定义为:

  1. #define va_end(ap) ( ap = (va_list)0 )

三、C语言scanf函数的实现

上文从理论上详细介绍了C语言中可变参数函数的实现,接下来从minix内核源码中的scanf函数入手,学习C语言经典可变参数函数的实现过程

在scanf.c文件中,可以看到scanf函数,代码如下:

  1. #include <stdio.h>
  2. #include <stdarg.h>
  3. #include "loc_incl.h"
  4.  
  5. int scanf(const char *format, ...)
  6. {
  7. va_list ap;
  8. int retval;
  9.  
  10. va_start(ap, format);
  11.  
  12. retval = _doscan(stdin, format, ap);
  13.  
  14. va_end(ap);
  15.  
  16. return retval;
  17. }

对于va_list、va_start、va_end等在stdarg.h头文件中定义的宏,都已经在上文中介绍过。

在上述代码中我们可以看到有一个_doscan函数,而这一函数在头文件loc_incl.h中定义,函数声明如下:

  1. int _doscan(FILE * stream, const char *format, va_list ap);

_doscan函数的实现源代码如下:

  1. int
  2. _doscan(register FILE *stream, const char *format, va_list ap)
  3. {
  4. int done = ; /* number of items done */
  5. int nrchars = ; /* number of characters read */
  6. int conv = ; /* # of conversions */
  7. int base; /* conversion base */
  8. unsigned long val; /* an integer value */
  9. register char *str; /* temporary pointer */
  10. char *tmp_string; /* ditto */
  11. unsigned width = ; /* width of field */
  12. int flags; /* some flags */
  13. int reverse; /* reverse the checking in [...] */
  14. int kind;
  15. register int ic = EOF; /* the input character */
  16. #ifndef NOFLOAT
  17. long double ld_val;
  18. #endif
  19.  
  20. if (!*format) return ;
  21.  
  22. while () {
  23. if (isspace(*format)) {
  24. while (isspace(*format))
  25. format++; /* skip whitespace */
  26. ic = getc(stream);
  27. nrchars++;
  28. while (isspace (ic)) {
  29. ic = getc(stream);
  30. nrchars++;
  31. }
  32. if (ic != EOF) ungetc(ic,stream);
  33. nrchars--;
  34. }
  35. if (!*format) break; /* end of format */
  36.  
  37. if (*format != '%') {
  38. ic = getc(stream);
  39. nrchars++;
  40. if (ic != *format++) break; /* error */
  41. continue;
  42. }
  43. format++;
  44. if (*format == '%') {
  45. ic = getc(stream);
  46. nrchars++;
  47. if (ic == '%') {
  48. format++;
  49. continue;
  50. }
  51. else break;
  52. }
  53. flags = ;
  54. if (*format == '*') {
  55. format++;
  56. flags |= FL_NOASSIGN;
  57. }
  58. if (isdigit (*format)) {
  59. flags |= FL_WIDTHSPEC;
  60. for (width = ; isdigit (*format);)
  61. width = width * + *format++ - '';
  62. }
  63.  
  64. switch (*format) {
  65. case 'h': flags |= FL_SHORT; format++; break;
  66. case 'l': flags |= FL_LONG; format++; break;
  67. case 'L': flags |= FL_LONGDOUBLE; format++; break;
  68. }
  69. kind = *format;
  70. if ((kind != 'c') && (kind != '[') && (kind != 'n')) {
  71. do {
  72. ic = getc(stream);
  73. nrchars++;
  74. } while (isspace(ic));
  75. if (ic == EOF) break; /* outer while */
  76. } else if (kind != 'n') { /* %c or %[ */
  77. ic = getc(stream);
  78. if (ic == EOF) break; /* outer while */
  79. nrchars++;
  80. }
  81. switch (kind) {
  82. default:
  83. /* not recognized, like %q */
  84. return conv || (ic != EOF) ? done : EOF;
  85. break;
  86. case 'n':
  87. if (!(flags & FL_NOASSIGN)) { /* silly, though */
  88. if (flags & FL_SHORT)
  89. *va_arg(ap, short *) = (short) nrchars;
  90. else if (flags & FL_LONG)
  91. *va_arg(ap, long *) = (long) nrchars;
  92. else
  93. *va_arg(ap, int *) = (int) nrchars;
  94. }
  95. break;
  96. case 'p': /* pointer */
  97. set_pointer(flags);
  98. /* fallthrough */
  99. case 'b': /* binary */
  100. case 'd': /* decimal */
  101. case 'i': /* general integer */
  102. case 'o': /* octal */
  103. case 'u': /* unsigned */
  104. case 'x': /* hexadecimal */
  105. case 'X': /* ditto */
  106. if (!(flags & FL_WIDTHSPEC) || width > NUMLEN)
  107. width = NUMLEN;
  108. if (!width) return done;
  109.  
  110. str = o_collect(ic, stream, kind, width, &base);
  111. if (str < inp_buf
  112. || (str == inp_buf
  113. && (*str == '-'
  114. || *str == '+'))) return done;
  115.  
  116. /*
  117. * Although the length of the number is str-inp_buf+1
  118. * we don't add the 1 since we counted it already
  119. */
  120. nrchars += str - inp_buf;
  121.  
  122. if (!(flags & FL_NOASSIGN)) {
  123. if (kind == 'd' || kind == 'i')
  124. val = strtol(inp_buf, &tmp_string, base);
  125. else
  126. val = strtoul(inp_buf, &tmp_string, base);
  127. if (flags & FL_LONG)
  128. *va_arg(ap, unsigned long *) = (unsigned long) val;
  129. else if (flags & FL_SHORT)
  130. *va_arg(ap, unsigned short *) = (unsigned short) val;
  131. else
  132. *va_arg(ap, unsigned *) = (unsigned) val;
  133. }
  134. break;
  135. case 'c':
  136. if (!(flags & FL_WIDTHSPEC))
  137. width = ;
  138. if (!(flags & FL_NOASSIGN))
  139. str = va_arg(ap, char *);
  140. if (!width) return done;
  141.  
  142. while (width && ic != EOF) {
  143. if (!(flags & FL_NOASSIGN))
  144. *str++ = (char) ic;
  145. if (--width) {
  146. ic = getc(stream);
  147. nrchars++;
  148. }
  149. }
  150.  
  151. if (width) {
  152. if (ic != EOF) ungetc(ic,stream);
  153. nrchars--;
  154. }
  155. break;
  156. case 's':
  157. if (!(flags & FL_WIDTHSPEC))
  158. width = 0xffff;
  159. if (!(flags & FL_NOASSIGN))
  160. str = va_arg(ap, char *);
  161. if (!width) return done;
  162.  
  163. while (width && ic != EOF && !isspace(ic)) {
  164. if (!(flags & FL_NOASSIGN))
  165. *str++ = (char) ic;
  166. if (--width) {
  167. ic = getc(stream);
  168. nrchars++;
  169. }
  170. }
  171. /* terminate the string */
  172. if (!(flags & FL_NOASSIGN))
  173. *str = '\0';
  174. if (width) {
  175. if (ic != EOF) ungetc(ic,stream);
  176. nrchars--;
  177. }
  178. break;
  179. case '[':
  180. if (!(flags & FL_WIDTHSPEC))
  181. width = 0xffff;
  182. if (!width) return done;
  183.  
  184. if ( *++format == '^' ) {
  185. reverse = ;
  186. format++;
  187. } else
  188. reverse = ;
  189.  
  190. for (str = Xtable; str < &Xtable[NR_CHARS]
  191. ; str++)
  192. *str = ;
  193.  
  194. if (*format == ']') Xtable[*format++] = ;
  195.  
  196. while (*format && *format != ']') {
  197. Xtable[*format++] = ;
  198. if (*format == '-') {
  199. format++;
  200. if (*format
  201. && *format != ']'
  202. && *(format) >= *(format -)) {
  203. int c;
  204.  
  205. for( c = *(format -) +
  206. ; c <= *format ; c++)
  207. Xtable[c] = ;
  208. format++;
  209. }
  210. else Xtable['-'] = ;
  211. }
  212. }
  213. if (!*format) return done;
  214.  
  215. if (!(Xtable[ic] ^ reverse)) {
  216. /* MAT 8/9/96 no match must return character */
  217. ungetc(ic, stream);
  218. return done;
  219. }
  220.  
  221. if (!(flags & FL_NOASSIGN))
  222. str = va_arg(ap, char *);
  223.  
  224. do {
  225. if (!(flags & FL_NOASSIGN))
  226. *str++ = (char) ic;
  227. if (--width) {
  228. ic = getc(stream);
  229. nrchars++;
  230. }
  231. } while (width && ic != EOF && (Xtable[ic] ^ reverse));
  232.  
  233. if (width) {
  234. if (ic != EOF) ungetc(ic, stream);
  235. nrchars--;
  236. }
  237. if (!(flags & FL_NOASSIGN)) { /* terminate string */
  238. *str = '\0';
  239. }
  240. break;
  241. #ifndef NOFLOAT
  242. case 'e':
  243. case 'E':
  244. case 'f':
  245. case 'g':
  246. case 'G':
  247. if (!(flags & FL_WIDTHSPEC) || width > NUMLEN)
  248. width = NUMLEN;
  249.  
  250. if (!width) return done;
  251. str = f_collect(ic, stream, width);
  252.  
  253. if (str < inp_buf
  254. || (str == inp_buf
  255. && (*str == '-'
  256. || *str == '+'))) return done;
  257.  
  258. /*
  259. * Although the length of the number is str-inp_buf+1
  260. * we don't add the 1 since we counted it already
  261. */
  262. nrchars += str - inp_buf;
  263.  
  264. if (!(flags & FL_NOASSIGN)) {
  265. ld_val = strtod(inp_buf, &tmp_string);
  266. if (flags & FL_LONGDOUBLE)
  267. *va_arg(ap, long double *) = (long double) ld_val;
  268. else
  269. if (flags & FL_LONG)
  270. *va_arg(ap, double *) = (double) ld_val;
  271. else
  272. *va_arg(ap, float *) = (float) ld_val;
  273. }
  274. break;
  275. #endif
  276. } /* end switch */
  277. conv++;
  278. if (!(flags & FL_NOASSIGN) && kind != 'n') done++;
  279. format++;
  280. }
  281. return conv || (ic != EOF) ? done : EOF;
  282. }

在上面的源代码中,值得注意的是第26行的getc宏,定义代码如下:

  1. #define getc(p) (--(p)->_count >= 0 ? (int) (*(p)->_ptr++) : \
  2. __fillbuf(p))

getc的调用形式:ch=getc(fp); 功能是从文件指针指向的文件读入一个字符,并把它作为函数值返回给int型变量ch。

第4行~第17行,定义一些后面需要用到的变量

第23行~第34行,跳过format格式串中的空格,并且跳过输入流中的空格

第37行~第42行,输入流stream与format格式串中的空白符(空白符可以是空格(space)、制表符(tab)和新行符(newline))保持一致

第44行~第52行,在format中的字符为'%'的前提下,stream中的字符也为'%',则继续

第54行~第57行,format当前字符为'*',表示读指定类型的数据但不保存

第58行~第62行,指定说明最大域宽。 在百分号(%)与格式码之间的整数用于限制从对应域读入的最大字符数于宽度

第64行~第282行,switch语句,用于格式修饰符,这些修饰符包括: h、l、L、c、p、b、d、i、o、u……,还有基于扫描集的'['修饰符


对scanf函数的源码分析,需要在scanf函数的语法格式详细的理解基础上进行,由于scanf函数实现十分复杂,需要仔细的品味,这里只是比较初步的分析,具体还有待后期不断的完善

C语言可变参数函数实现原理的更多相关文章

  1. C语言中可变参数函数实现原理

    C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关,正常情况下C的函数参数入栈规则为__stdcall, 它是从右到左的,即函数中的最右边的参数最先入栈.例如,对于函数: void fu ...

  2. c语言可变参数函数

    c语言支持可变参数函数.这里的可变指,函数的参数个数可变. 其原理是,一般情况下,函数参数传递时,其压栈顺序是从右向左,栈在虚拟内存中的增长方向是从上往下.所以,对于一个函数调用 func(int a ...

  3. C语言可变参数函数的编写

    1. 引言 C语言我们接触的第一个库函数是 printf(“hello,world!”);其参数个数为1个. 然后,我们会接触到诸如: printf(“a=%d,b=%s,c=%c”,a,b,c);此 ...

  4. C语言可变参数函数详解示例

    先看代码 printf(“hello,world!”);其参数个数为1个. printf(“a=%d,b=%s,c=%c”,a,b,c);其参数个数为4个. 如何编写可变参数函数呢?我们首先来看看pr ...

  5. [11 Go语言基础-可变参数函数]

    [11 Go语言基础-可变参数函数] 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数. 语法 如果函数最后一个参数被记作 ...T ,这时函数可以接受任意个 T 类型参数作为最 ...

  6. C语言中的可变参数函数

    C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为: int printf( const char* format, ...); 它除了有一个参数format固定以外 ...

  7. C语言学习020:可变参数函数

    顾名思义,可变参数函数就是参数数量可变的函数,即函数的参数数量是不确定的,比如方法getnumbertotal()我们即可以传递一个参数,也可以传递5个.6个参数 #include <stdio ...

  8. C语言 可变参数

    一.基础部分 1.1 什么是可变长参数 可变长参数:顾名思义,就是函数的参数长度(数量)是可变的.比如 C 语言的 printf 系列的(格式化输入输出等)函数,都是参数可变的.下面是 printf ...

  9. C可变参数函数 实现

    转自:http://blog.csdn.net/weiwangchao_/article/details/4857567 C函数要在程序中用到以下这些宏: void va_start( va_list ...

随机推荐

  1. MyBatis知多少(15)数据模型

    瘦数据模型是一种最为臭名昭著并且问题多多的对关系数据库系统的滥用.不幸的是,有时又的确需要瘦数据模型.所谓瘦数据模型,就是简单地将每张表都设计为一种通用数据结构,用于存储名值对的集合.这非常像Java ...

  2. 玩转PowerShell第一节——【后台任务处理】-技术&分享

    概述 相信大家对后台任务处理不陌生,比如.Net的后台线程处理,Java的线程处理等等. 而当我们用PowerShell这个强大的工具时怎么样开启后台任务呢,以及怎样处理这些任务呢,本篇将会告诉你Po ...

  3. 15套精美的免费界面设计 PSD 素材【免费下载】

    在这个集合中,我们聚集15套精美的 PSD 界面设计模板,网页元素,用户界面​​工具包,扁平化图标,APP 应用程序 UI 设计的等等.这些来自优秀设计师的 PSD 源文件素材让其它的设计师们在设计用 ...

  4. 关于eclipse中MAVEN WEB工程中编译问题

    这几天是被java的环境搞疯了,我先是搭了一个spring+springmvc+mybatis的工程,在家里跑了一下,没有问题,把工程带到公司里用,却一直不能使用. 按常理来说,只要工程发生一点变化, ...

  5. Entity Framework优缺点及使用方法总结

    Entity Framework是M$提供的一个ORM框架,它旨在为小型应用程序中数据层的快速开发提供便利. nuget上185W多的下载量,说明.Net开发人员还是比较喜欢用EF的.但是EF在提供了 ...

  6. MEF(Managed Extensibility Framework )的入门介绍

    1.什么是MEF MEF是一个来自于微软协作构建扩展应用的新框架,它的目的是在运行中的应用中添加插件.MEF继承于.NET 4.0 Framework平台,存在于各种应用平台的系统程序集中 2.程序集 ...

  7. C#类的继承相关总结

    1.子类继承父类,会拥有父类中所规范的所有成员,但是只能是使用其中的公共成员 2.实现了继承,可以做到代码的冗余,做到代码的重用 3.实现了继承,可以方便代码的扩展与修改 4,当子类拥有与父类相同签名 ...

  8. 周末web前端练习

    在 CSS 样式定义中,以下哪种 RGB 颜色值是 Web 安全色?   A]#111111B]#222222C]#333333D]#444444 答案:http://hovertree.com/ti ...

  9. java俄罗斯方块游戏代码

    java俄罗斯方块游戏代码: package com; import java.awt.Color; import java.awt.Graphics; import java.awt.event.K ...

  10. csharp: Flash Player play *.flv file in winform

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...