大家熟知的C库函数printf函数就是一个可变参数函数,它是怎么实现的呢?不过他实现是有条件的,必须函数参数的入栈顺序为从右向左的顺序,也即函数的形参,在函数调用之前,必须是最右边的参数先入栈,并且参数都必须通过栈传递,以1个例子说明,如函数func(arg1, arg2,arg3),那么函数的堆栈应是:

ebp是帧指针寄存器,一般用来存取堆栈,有了堆栈结构,下面我们看看C可变参数的具体实现原理:

  1. #include <stdio.h>
  2. enum {
  3. ptChar,
  4. ptInt,
  5. ptFloat,
  6. ptDouble,
  7. };
  8. void printSum(unsigned long paramFormat, ...)
  9. {
  10. /*高16位为可变参数类型,低16位为可变参数个数*/
  11. int paramType = (paramFormat >> 16);
  12. int paramNum = paramFormat & 0xffff;
  13. /*¶mFormat = ebp + 8,第一个参数的地址*/
  14. unsigned long *pArg = ¶mFormat;
  15. /*ebp + 0x0c, 第二个参数地址*/
  16. pArg++;
  17. switch(paramType)
  18. {
  19. case ptChar:
  20. {
  21. int sum = 0;
  22. for (int i = 0; i < paramNum; i++)
  23. {
  24. char *pValue = (char *)pArg;
  25. sum += *pValue;
  26. pArg++;
  27. }
  28. printf("%d\n", sum);
  29. }
  30. break;
  31. case ptInt:
  32. {
  33. int sum = 0;
  34. for (int i = 0; i < paramNum; i++)
  35. {
  36. int *pValue = (int *)pArg;
  37. sum += *pValue;
  38. pArg++;
  39. }
  40. printf("%d\n", sum);
  41. }
  42. break;
  43. case ptFloat:
  44. {
  45. float sum = 0;
  46. /**/
  47. pArg++;
  48. /*浮点参数,堆栈占8个字节,所以指针偏移为8*/
  49. for (int i = 0; i < paramNum; i++)
  50. {
  51. float *pValue = (float *)pArg;
  52. sum += *pValue;
  53. pArg++;
  54. pArg++;
  55. }
  56. printf("%f\n", sum);
  57. }
  58. break;
  59. case ptDouble:
  60. {
  61. double sum = 0;
  62. /*双精度浮点参数,堆栈占8个字节,所以指针偏移为8*/
  63. for (int i = 0; i < paramNum; i++)
  64. {
  65. double *pValue = (double *)pArg;
  66. sum += *pValue;
  67. pArg++;
  68. pArg++;
  69. }
  70. printf("%f\n", sum);
  71. }
  72. break;
  73. default:
  74. printf("unknowned type!\n");
  75. break;
  76. }
  77. }
  78. void main()
  79. {
  80. unsigned long paramFormat = 3;
  81. char a = 1, b = 2, c = 3;
  82. printSum(paramFormat, a, b, c);
  83. paramFormat = ptInt << 16;
  84. paramFormat += 3;
  85. int ia = 1, ib = 2, ic = 3;
  86. printSum(paramFormat, ia, ib, ic);
  87. paramFormat = ptFloat << 16;
  88. paramFormat += 3;
  89. float fa = 1, fb = 2, fc = 3;
  90. printSum(paramFormat, fa, fb, fc);
  91. paramFormat = ptDouble << 16;
  92. paramFormat += 3;
  93. double da = 1, db = 2, dc = 3;
  94. printSum(paramFormat, da, db, dc);
  95. }

上面这种方法对函数参数的入栈顺序有限制,必须从右向左入栈,这就是为什么pascal调用方式不能实现printf的原因,并且函数形参都要通过栈来传递,这对有些编译器为了优化处理,函数参数通过寄存器来传递,从而不满足要求。鉴于次,本文采用C++的默认形参实现可变参数的方法,没有上面的这些限制,下面是实现代码:

  1. #include <stdio.h>
  2. enum {
  3. ptChar,
  4. ptInt,
  5. ptFloat,
  6. ptDouble,
  7. };
  8. void printSum(unsigned long paramType,
  9. void *arg1 = NULL,
  10. void *arg2 = NULL,
  11. void *arg3 = NULL,
  12. void *arg4 = NULL,
  13. void *arg5 = NULL,
  14. void *arg6 = NULL,
  15. void *arg7 = NULL,
  16. void *arg8 = NULL,
  17. void *arg9 = NULL,
  18. void *arg10 = NULL)
  19. {
  20. void *arg[10] = {
  21. arg1,
  22. arg2,
  23. arg3,
  24. arg4,
  25. arg5,
  26. arg6,
  27. arg7,
  28. arg8,
  29. arg9,
  30. arg10,
  31. };
  32. switch(paramType)
  33. {
  34. case ptChar:
  35. {
  36. int sum = 0;
  37. for (int i = 0; i < 10; i++)
  38. {
  39. if (arg[i] != NULL)
  40. {
  41. char *pValue = (char *)arg[i];
  42. sum += *pValue;
  43. }
  44. else
  45. break;
  46. }
  47. printf("%d\n", sum);
  48. }
  49. break;
  50. case ptInt:
  51. {
  52. int sum = 0;
  53. for (int i = 0; i < 10; i++)
  54. {
  55. if (arg[i] != NULL)
  56. {
  57. int *pValue = (int *)arg[i];
  58. sum += *pValue;
  59. }
  60. else
  61. break;
  62. }
  63. printf("%d\n", sum);
  64. }
  65. break;
  66. case ptFloat:
  67. {
  68. float sum = 0;
  69. for (int i = 0; i < 10; i++)
  70. {
  71. if (arg[i] != NULL)
  72. {
  73. float *pValue = (float *)arg[i];
  74. sum += *pValue;
  75. }
  76. else
  77. break;
  78. }
  79. printf("%f\n", sum);
  80. }
  81. break;
  82. case ptDouble:
  83. {
  84. double sum = 0;
  85. for (int i = 0; i < 10; i++)
  86. {
  87. if (arg[i] != NULL)
  88. {
  89. double *pValue = (double *)arg[i];
  90. sum += *pValue;
  91. }
  92. else
  93. break;
  94. }
  95. printf("%f\n", sum);
  96. }
  97. break;
  98. default:
  99. printf("unknowned type!\n");
  100. break;
  101. }
  102. }
  103. void main()
  104. {
  105. unsigned long paramType = ptChar;
  106. char a = 1, b = 2, c = 3;
  107. printSum(paramType, &a, &b, &c);
  108. paramType = ptInt;
  109. int ia = 1, ib = 2, ic = 3;
  110. printSum(paramType, &ia, &ib, &ic);
  111. paramType = ptFloat;
  112. float fa = 1, fb = 2, fc = 3;
  113. printSum(paramType, &fa, &fb, &fc);
  114. paramType = ptDouble;
  115. double da = 1, db = 2, dc = 3;
  116. printSum(paramType, &da, &db, &dc);
  117. }

http://blog.csdn.net/rabinsong/article/details/8946514

C++可变参数的另一种实现的更多相关文章

  1. c 可变参数 定义可变参数的函数

    定义可变参数的函数,需要在stdarg.h头文件中定义的va_list类型和va_start.va_arg.va_end三个宏. 定义可变参数函数 va_list ap;  //实际是定义一个指针va ...

  2. 理解 Python 中的可变参数 *args 和 **kwargs:

    默认参数:  Python是支持可变参数的,最简单的方法莫过于使用默认参数,例如: def getSum(x,y=5): print "x:", x print "y:& ...

  3. Java 传递可变参数和方法重载

    形式:类型... 参数名 示例:public void show(int... a) {}; 可变参数在方法中被当作数组来处理 可变参数传值的四种方式: 一个值也不传,可变参数会接收到长度为0的数组 ...

  4. define可变参数,float数据传输

    define可变参数 一般在调试打印Debug信息的时候, 需要可变参数的宏. 从C99开始可以使编译器标准支持可变参数宏(variadic macros), 另外GCC也支持可变参数宏, 但是两种在 ...

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

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

  6. 嵌入式C语言自我修养 12:有一种宏,叫可变参数宏

    12.1 什么是可变参数宏 在上面的教程中,我们学会了变参函数的定义和使用,基本套路就是使用 va_list.va_start.va_end 等宏,去解析那些可变参数列表我们找到这些参数的存储地址后, ...

  7. 大数据学习day13------第三阶段----scala01-----函数式编程。scala以及IDEA的安装,变量的定义,条件表达式,for循环(守卫模式,推导式,可变参数以及三种遍历方式),方法定义,数组以及集合(可变和非可变),数组中常用的方法

    具体见第三阶段scala-day01中的文档(scala编程基础---基础语法)  1. 函数式编程(https://www.cnblogs.com/wchukai/p/5651185.html): ...

  8. C可变参数的函数

    我们实现一个简单的printf函数(可变参数) #include <stdio.h> #include <stdarg.h> void myprintf(const char ...

  9. 可变参数列表与printf()函数的实现

    问题 当我们刚开始学习C语言的时候,就接触到printf()函数,可是当时"道行"不深或许不够细心留意,又或者我们理所当然地认为库函数规定这样就是这样,没有发现这个函数与普通的函数 ...

随机推荐

  1. Python核心编程读笔 7: 条件和循环

    第八章 条件和循环 一.if python中的条件表达式:很奇葩!!! smaller = (x < y and [x] or [y])[0] 或者: smaller = x if x < ...

  2. leetcode Valid Parentheses python

    # 解题思路: # 创建一个字典映射关系 dicts# 使用一个栈stk 遍历字符串s 得到一个新的字符串curItem 如果lastItem在dicts中的value和它相等 不做任何操作# 如果不 ...

  3. C语言——strlen()和sizeof的区别

    strlen()和sizeof()的区别: strlen()——>C字符串库函数,返回字符串的真实长度.它是从内存某位置开始扫描,直到碰到结束符'\0'停止,返回计数器值. sizeof()—— ...

  4. select * from (select P.*,ROWNUM RN FROM(select * from Mp_Relatedart where pubbaseid=785 order by ID ASC )P)M WHERE M.RN>2 and M.RN <= 7

    select * from (select P.*,ROWNUM RN FROM(select * from Mp_Relatedart where pubbaseid=785 order by ID ...

  5. less的学习(css)

    因为新公司需要用less来写样式,对于用惯了css的我来说还是觉得有点麻烦 但是呢,都是有个过程嘛,学习必须走起嘛. 写到半中央发现一个写的特别好的less帖子,就不写. http://www.w3c ...

  6. HTML5 canvas易错点

    一.画布的默认宽高 <canvas id="myCanvas" style="border:1px solid black;"> 你的浏览器不支持h ...

  7. 在git彻底删除commit记录的方法是什么?

    在github上,如果非默认分支的话,直接用以下方法: git reset --hard <commit_id> git push origin HEAD --force 如是默认分支,需 ...

  8. poj 2274 The Race 最小堆

    题目链接 题目大意: 给n个小车, 每个小车有一个初始位置x和初始速度v, 保证x1 < x2..... <xn. 0<v<100. 然后问你一共会发生多少次超车, 以及前10 ...

  9. GoF——组合模式

    组合模式:将对象组合成树形结构以表示“部分-真题”的结构层次.组合模式使得用户对单个对象和组合对象的使用具有一致性. 结构图: using System; using System.Collectio ...

  10. OSG事件回调

    OSG中的节点主要使用回调(CallBack)来完成用户临时.需要每帧执行的工作.根据回调功能被调用的时机划分为更新回调(Update CallBack)和人机交互时间回调(Event CallBac ...