`
weiyuhu
  • 浏览: 237791 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

变长参数的实现

阅读更多
我们用的最多的C函数是哪个?毫无疑问,是printf。但是你看过printf的声明式吗,那是相当诡异。随便拿一本带C库函数参考的书,可查到如下结果:
             int printf(const char *format, ...);
那三个连续的点就代表大于或等于0个参数,再加上前面的format参数,所以printf函数至少要接受一个字符串,后面就随便了。但是这是如何实现的呢,不急,首先必须要了解C语言函数参数传递的机制。

C语言函数参数传递机制

我们知道,每个程序都有个用户栈,所有的函数调用都要使用它,比如被调用函数的实参及局部变量都要存在用户栈中,所以每个函数都有个栈帧。下面我用两个函数来说明这种调用机制:
int main(void)                             int add(int param1, int param2)
{                                          {
    int a, b, c;                               int d;
    a = 2;                                     d = param1 + param2;
    b = 3;                                     return param1+param2;
    c = add(a,b);                          }
    return 0;

}
main 函数在调用add函数时,main负责把b,a及eip(即返回地址)按顺序压栈,注意栈是向下增长的。然后执行call指令调用add函数。(ps: 其实在返回地址和局部变量d之间函数add还会压入一些寄存器,但是这对我们理解变长参数没有影响,总之要记住参数是自右向左压栈的)
                             

处理变长参数的宏定义
处理变长参数说白了就是怎样获取那些在声明中没明说的参数,比如在printf("%d equals to %d.", a, b)中,第一个字符串参数可直接通过format取得,但a和b呢?这就要需要对参数传递的理解了。主函数调用printf时肯定是先压入b,再压入a,最后压入"%d equals to %d"的地址。
现在我们来获取a的地址,很简单,当然是 (char*)&format+sizeof(const char*)。其中&format是参数format在栈中的地址,而a正好format上面。

OK, 现在我们可以系统地讲解处理变长参数的宏了。
在头文件stdarg.h中先定义个类型 va_list,它是专门用来去可变参数的,如果要对付可变参数首先要定义一个va_list类型的变量ap:
            typedef char* va_list;
因为在获取参数地址时必须知道它前一个参数的数据类型大小,所以要定义__va_sizeof(type),它的作用和sizeof一样,不过__va_sizeof(char),__va_sizeof(short)的值都是4:
            #define __va_sizeof(type) (((sizeof(type) + sizeof(int) - 1) / sizeof(int)) * sizeof(int))     
然后定义va_start获取第一个可变参数的地址:
            #define va_start(ap, last) ((ap) = (va_list)&(last) + __va_size(last))
va_arg用于获取ap当前指向的参数的值,并顺带把va_list移向下一个参数
            #define va_arg(ap, type) (*(type *)((ap) += __va_size(type), (ap) - __va_size(type)))
当用完ap时,用va_end将其赋值为空,也就是0
            #define va_end(ap) (ap)=0


处理变长参数的一般框架
如果我们自己实现printf函数,代码的形式大概如下
int chparamf(const char *format, ...)
{
    va_list ap;
    va_start(ap,format);
    循环使用 va_arg(ap,你要取的参数的数据类型)
    va_end(ap);
}
0
0
0
(请您对文章做出评价)
分享到:
评论

相关推荐

    C语言变长参数C语言变长参数C语言变长参数

    首先,要实现变长参数的函数,必须至少有一个固定的参数,用于指示后续变长参数的格式。例如,`printf`函数的第一个参数通常是格式字符串`const char *fmt`。这个参数提供了关于后续参数类型和顺序的信息。 C语言...

    CC++变长参数用法

    ### C/C++中的变长参数详解 在C/C++编程语言中,变长参数列表(variadic function)是指函数能够接受可变数量的参数。这种灵活性使得开发人员能够在不确定参数个数的情况下定义函数,这对于创建诸如日志记录、错误...

    从printf谈可变参数函数的实现

    在`printf`的实现中,首先,`va_start`宏被用来初始化一个`va_list`类型的变量`args`,`va_list`是一个指向变长参数列表的指针。`va_start`宏接收两个参数,一个是`args`,另一个是`fmt`,即格式字符串。`fmt`在这里...

    C语言变长参数1

    然而,如果不在标准库的帮助下,我们能否自定义实现变长参数的功能呢? 首先,我们需要理解变长参数的基本规则。在C语言中,任何包含变长参数的函数必须至少有一个固定参数,这个固定参数通常是一个格式字符串(如`...

    一个变长参数的例子 (参数不固定 源代码)

    下面我们将深入探讨变长参数的概念、实现方式以及在不同编程语言中的应用。 在C/C++中,变长参数通过`...`符号来表示,通常配合`va_list`、`va_start`、`va_arg`和`va_end`宏进行操作。例如,以下是一个简单的变长...

    变长参数 编译期类型识别技术

    本篇文章将深入探讨如何利用函数模板递归实例化来实现编译期的变长参数类型识别。 首先,我们要理解C++的变长参数机制。在C++98中,我们通常使用`stdarg.h`库中的`va_list`、`va_start`、`va_arg`和`va_end`宏来...

    关于java中可变长参数的定义及使用方法详解

    5. **效率考虑**:可变长参数在内部使用数组实现,因此在大量参数传递时可能会有性能损失,尤其是如果参数需要进行深拷贝或者转换操作。 总的来说,Java的可变长参数提供了一种灵活的编程方式,使得我们可以方便地...

    C#变长参数表求平均数程序源码.zip

    在这个"变长参数表求平均数程序源码"中,我们可以看到如何利用C#的varargs(变长参数)特性来实现一个动态计算平均数的函数。 首先,我们要理解什么是变长参数。在C#中,使用`params`关键字可以定义一个变长参数。...

    java可变长参数(三个点)md,学习代码

    Java中的可变长参数是Java 5引入的一个重要特性,它允许我们在定义方法时使用一个类型后跟三个点(...)来表示该方法可以接受任意数量的参数。这个特性极大地提高了代码的灵活性和便利性,使得我们可以以更简洁的...

    Kotlin传递可变长参数给Java可变参数实例代码

    在本文中,我们将介绍Kotlin传递可变长参数给Java可变参数实例代码的实现方法。 Java可变参数方法 ---------------- 在Java中,我们可以使用可变参数来定义方法,例如: ```java public class CallJavaUtils { ...

    C51可变参数讲解.pdf

    C51可变参数函数允许程序员设计能接受不定数量参数的函数,这在C语言中是通过头文件stdarg.h提供的宏实现的。stdarg.h在Keil C51环境中也提供了对可变参数函数的支持,可以让嵌入式开发人员灵活地编写函数,以处理...

    c/C++可变参数函数的参数传递机制剖析

    - **参数传递机制**:使用 `va_list` 类型的变量以及 `va_start`、`va_arg` 和 `va_end` 宏来实现对可变参数的访问和管理。 - **灵活性和实用性**:通过这种方法,开发者可以在不知道具体参数数量的情况下编写灵活的...

    TIA博途中变长数组的介绍与使用入门示例.docx

    3. **操作方法**:处理变长数组时,通常需要判断数组的上下界,这可以通过`LOWER_BOUND`和`UPPER_BOUND`函数实现。之后,可以使用循环语句(如`FOR`循环)遍历数组,对每个元素进行操作。 以下是一个简单的示例,...

    变长文件存取类库 B+树实现

    本文将详细解析"变长文件存取类库 B+树实现"的相关知识点,以及如何通过模板对B+树进行抽象以提高代码的可复用性和灵活性。 首先,理解B+树(B Plus Tree)的基本概念至关重要。B+树是一种自平衡的多路搜索树,具有...

    C语言变长数组之剖析

    在使用变长数组时,需要特别注意其内存分配和释放的特性,以及在函数参数传递中的行为。由于数组的大小在运行时确定,这意味着传递变长数组给函数时,需要考虑内存分配和管理的责任划分。在某些情况下,可能需要通过...

    变长记录文件存取类库及测试

    标题中的"变长记录文件存取类库及测试"指的是一个C++实现的类库,专门用于处理变长记录文件。这个类库设计得功能强大,不仅包含了完整的代码,还具有超强的容错性,这意味着它能够在面对各种异常情况时依然保持稳定...

    javascript 利用arguments实现可变长参数

    在C#等其他编程语言中,`params`关键字用于定义可变长参数,但在JavaScript中,`arguments`对象提供了类似的功能。 `arguments`对象在每个函数内部都是隐式存在的,它是一个类数组对象,包含了函数调用时传入的所有...

    Python函数中的可变长参数详解

    本文将深入探讨Python函数中的可变长参数,即那些允许函数接受不确定数量参数的功能。 首先,让我们了解Python函数参数的三种基本类型: 1. 位置参数:这是最基础的参数类型,调用函数时按照顺序传入值,函数定义...

Global site tag (gtag.js) - Google Analytics