可变参数

作者: 罗蓁蓁 | 来源:发表于2020-04-28 19:35 被阅读0次

C语言的可变参数的实现非常巧妙:大师只用了 3 个宏就解决了这个难题。

一、可变参数的应用

这里实现一个简单的可变参数函数 sum:它将个数不定的多个整型参数求和后返回,其第 1 个参数指明了要相加的数的个数(va.c):

#include <stdio.h>
#include <stdarg.h>

// 要相加的整数的个数为 n
int sum(int n, ...)
{
    va_list ap;
    va_start(ap, n);

    int ans = 0;
    while(n--)
        ans += va_arg(ap, int);

    va_end(ap);
    return ans;
}

int main()
{
    int ans = sum(2, 3, 4);
    printf("%d\n", ans);

    return 0;
}

sum 函数的第一个参数是 int n,逗号后面是连续的 3 个英文句点,表示参数 n 之后可以跟 0、1、2…… 个任意类型的参数。
sum 可以这么用:

sum(0);
sum(1, 2);
sum(3, 1, 1, 1);

二、可变参数的实现

可以看到在 sum 函数中用到了 3 个函数一样的东西:

  • va_start
  • va_arg
  • va_end

它们是标准库(意味着各种平台都有)头文件 stdarg.h 中定义的宏,这 3 个宏经过清理后是下面这个样子:

typedef char* va_list;
#define va_start(ap,v)  ( ap = (va_list)(&v) + sizeof(v) )
#define va_arg(ap,t)    ( *(t *)((ap += sizeof(t)) - sizeof(t)) )
#define va_end(ap)      ( ap = NULL )
  • va_start 将 ap 定位到可变参数列表的起始地址
  • va_arg 每次返回一个参数,并后移 ap 指针
  • va_end 将 ap 置 NULL(避免非法使用)

这 3 个宏的实现就是基于 C语言默认调用惯例是从右至左将参数压栈的事实,比如说 va.c 中调用 sum 函数,参数压栈的顺序为:4->3->2,又因为 x86 CPU 的栈是向低地址增长的,所以参数的排列顺序如下:


1.jpg

va_start(n, ap)就是 ( ap = (char*)(&n) + 4 )。因此 ap 被赋值为 ebp+12 也就是变参列表的起始地址。之后 va_arg 取出每一个参数:

( *(int *)((ap += 4) - 4) )
它首先将变参指针 ap 右移到下一个参数的起始地址,再将加赋操作的返回值减到之前的位置取出一个参数。这样,用一条语句既取出了当前参数,又后移了指针 ap,真是神了!sum 中循环使用 va_arg 就取出了 n 个要相加的整数。

三、变参函数的可行性

一个变参函数能接受个数、类型可变的参数,需要满足以下两个条件:

  1. 能定位到可变参数列表的起始地址
  2. 能获知可变参数的个数、每个参数的大小(类型)

条件 1 只要有个前置参数就能满足,而对于这样的变参函数:void func(...);编译能通过,但是不能用 va_start 取到变参列表的起始地址,所以基本不可行。

sum 函数中参数 n 被用来定位可变参数列表的起始地址(满足条件1);n 的值是可变参数的个数,类型默认全部是 int 型(满足条件2),因此 sum 能正常工作。

再看看 printf 函数是如何满足以上两个条件的,printf 函数的原型是:

int printf(const char *fmt, ...);

printf 的第1个参数 fmt(格式串)被用来定位其后的可变参数的起始地址(满足条件1);
fmt 指向的字符串中的各个格式描述符如:%d、%lf、%s 等告诉了 printf fmt 之后参数的个数、各个参数的类型(满足条件2),因此 printf 能正常工作。

当然,sum、printf 能正常工作是设计者一厢情愿的期望,如果使用者不按规矩传入参数、格式串,函数能正常工作才怪!
比如:

sum(2, "111", "222");
printf("%s", 0);

编译器可不会进行可变参数的类型检查、格式串-参数匹配,后果将会在运行的时候出现……

出差必备

买火车票、高铁票、机票,订酒店都打9折的出行工具TRIP,点击注册

相关文章

  • 元组、字典可变参数

    可变参数 需求: 参数数量不确定,能否接收? args元组可变参数 kwargs字典可变参数 传递可变参数

  • 函数参数

    1、可变参数,参数个数可变 在参数前面加上* 就代表可变参数 可变参数,在函数内部其实就是一个tuple def ...

  • ★10.关于可变参数模板

    可变参数函数模板 可变参数类模板 可变参数函数模板的使用 转发参数包

  • 函数的,参数,变量作用域,递归,匿名函数

    一:函数参数的介绍 1:可变和不可变参数 1.1:必选参数 1.2:默认参数 1.3:可变参数 1.4:关键字参数...

  • Go语言可变参数

    可变参数 参数数量可变的函数称为可变参数函数 在声明可变参数函数时,需要在参数列表的最后一个参数类型之前加上省略符...

  • 可变参数列表

    可变参数列表 数组可以向可变参数传值,反之,不行 可变参数列表作为参数时只能放在参数的最后面 可变参数列表的方法是...

  • python函数回顾

    1.参数的介绍 (1)可变和不可变参数 (2)必选参数 (3)默认参数 (4)可变参数 (5)关键字参数 1.1....

  • Python参数 * 与 ** ,及装包拆包

    (*args)可变参数在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以...

  • 12. 可变参数函数

    12. 可变参数函数 什么是可变参数函数 可变参数函数是一种参数个数可变的函数。 语法 如果函数最后一个参数被记作...

  • Java中的可变参数

    学习笔记:可变参数,仅语法 可变参数又称参数的个数可变,用作方法形参的出现,那么方法的参数个数就是可变的了. 格式...

网友评论

    本文标题:可变参数

    本文链接:https://www.haomeiwen.com/subject/ttwbwhtx.html