【C 高阶】可变参数

本文介绍了C语言中可变参数的原理和使用方法,通过`printf()`函数为例,讲解了`<stdarg.h>`提供的`va_start`, `va_arg`, `va_end`三个API的用途,并给出了一个自定义的可变参数求和函数示例。文章还探讨了`printf()`如何根据格式化字符串确定可变参数个数,并简单提到了可变参数的实现原理。" 104019375,9271875,C语言实现矩阵相乘,"['c语言', '矩阵运算', '数值计算', '编程挑战']
摘要由CSDN通过智能技术生成

1. 简介

可否想过 C 语言中最常见常用的 printf() 函数是如何做到接收任意类型、任意数量的参数的呢?

实际上,printf() 中通过可变参数特性来接收任意类型、任意数量的参数。可变参数通过占位 ... 显式指示,例如以下函数的形参就是可变参数:

void fun (...)
{
    // do something
}

2. 相关 API

在函数中怎么使用这些可变参数呢?C 语言在头文件 <stdarg.h> 中提供了三个操作可变参数的 API:va_startva_argva_end,前缀 va 即 variable argument(可变参数)。

这三个 API 都为函数宏,功能如下:

  • va_start

va_start 的声明如下(伪代码):

void va_start (va_list ap, last);

ap 为可变参数列表对象,需要在调用 va_start 前创建。va_list 内包含一个成员指针,即 ap 内有指向可变参数的指针。

last 为可变参数的占位符前的形参。一般情况下,使用可变参数的函数需要提供一个形参用于函数调用时指定可变参数的个数,否则在程序中将无法得知具体的可变参数个数。

va_start 用于初始化可变参数列表对象,把 ap 内的可变参数指针指向 last 的下一个参数即可变参数列表的第一个参数。

  • va_arg

va_arg 的声明如下(伪代码):

type va_arg (va_list ap, type);

type 为可变参数的数据类型。

va_arg 用于获取 ap 的可变参数参数指针所指的参数并作为返回值。获取后该指针将根据 type 偏移一定字节数,指向下一个可变参数。

需要注意的是,使用 GCC 编译时,当可变参数类型为 char 或 short 即少于四个字节,可变参数将占据四个字节大小。因此,此时的函数实参 type 应显式指示为 int。

  • va_end

va_end 的声明如下(伪代码):

void va_end (va_list ap);

va_end 用于释放可变参数列表对象,需要在函数最后调用完成收尾工作。如果在释放后继续操作可变参数列表对象,结果是未知的。


3. 应用方法

前文提到,一般情况下,使用可变参数的函数需要提供一个形参用于函数调用时指定可变参数的个数。

以以下求和函数为例:

int sum(int n, ...)
{
    int sum = 0;

    va_list valist;
    va_start(valist, n);

    while (n--)
    {
        sum += va_arg(valist, int);
    }

    va_end(valist);

    return sum;
}

调用 sum() 函数时,指定可变参数的个数 n。在函数中,可以调用 n 次 va_arg 获取所有传递的可变参数。

但对于 printf() 而言,调用函数时并没有指定 n,那么它是怎样得知可变参数个数的呢?实际上,printf() 内会根据给定字符串中格式为 %<...> 的占位符推算出可变参数的个数。

现可实现简单的 printf() 函数:

int MyPrint(const char* s, ...)
{
    if (s == NULL)
    {
        return -1;
    }

    va_list valist;
    va_start(valist, s);

    while (*s)
    {
        if (*s == '%')
        {
            s++;
            switch (*s)
            {
                case 'c':
                {
                    putchar(va_arg(valist, int));
                    break;
                }
                case 's':
                {
                    const char* p = va_arg(valist, char*);
                    while (*p)
                    {
                        putchar(*p++);
                    }
                    break;
                }
                define :
                {
                    va_end(valist);
                    return -1;
                }
            }
            s++;
        }
        else
        {
            putchar(*s);
            s++;
        }
    }

    va_end(valist);
    return 0;
}

在程序中使用 MyPrint():

int main(int argc, char **argv)
{
	MyPrint("Hello World!\n");
	
	char* s = "123456";
	MyPrint("%s%s\n", s, "789");
}

编译后,运行结果如下:

kong@ubuntu:/mnt/hgfs/share/pj_cpp$ output/app 
Hello World!
123456789
kong@ubuntu:/mnt/hgfs/share/pj_cpp$ 

4. 实现原理

可变参数的实现与编译器强相关,不同编译器的实现方式很可能不一样。例如有的 32 位编译器中可变参数都存放在栈上,而有的 64 位编译器则将部分可变参数存放到寄存器上。

更详细的介绍有可见:揭密X86架构C可变参数函数实现原理


更多 C 高阶系列博文

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值